Imported Upstream version 2.64.5
[platform/upstream/glib.git] / gio / gdesktopappinfo.c
1 /* GIO - GLib Input, Output and Streaming Library
2  *
3  * Copyright (C) 2006-2007 Red Hat, Inc.
4  * Copyright © 2007 Ryan Lortie
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  * Author: Alexander Larsson <alexl@redhat.com>
20  *         Ryan Lortie <desrt@desrt.ca>
21  */
22
23 /* Prelude {{{1 */
24
25 #include "config.h"
26
27 /* For the #GDesktopAppInfoLookup macros; since macro deprecation is implemented
28  * in the preprocessor, we need to define this before including glib.h*/
29 #define GLIB_DISABLE_DEPRECATION_WARNINGS
30
31 #include <errno.h>
32 #include <string.h>
33 #include <unistd.h>
34
35 #ifdef HAVE_CRT_EXTERNS_H
36 #include <crt_externs.h>
37 #endif
38
39 #include "gcontenttypeprivate.h"
40 #include "gdesktopappinfo.h"
41 #ifdef G_OS_UNIX
42 #include "glib-unix.h"
43 #endif
44 #include "gfile.h"
45 #include "gioerror.h"
46 #include "gthemedicon.h"
47 #include "gfileicon.h"
48 #include <glib/gstdio.h>
49 #include "glibintl.h"
50 #include "giomodule-priv.h"
51 #include "gappinfo.h"
52 #include "gappinfoprivate.h"
53 #include "glocalfilemonitor.h"
54
55 #ifdef G_OS_UNIX
56 #include "gdocumentportal.h"
57 #endif
58
59 /**
60  * SECTION:gdesktopappinfo
61  * @title: GDesktopAppInfo
62  * @short_description: Application information from desktop files
63  * @include: gio/gdesktopappinfo.h
64  *
65  * #GDesktopAppInfo is an implementation of #GAppInfo based on
66  * desktop files.
67  *
68  * Note that `<gio/gdesktopappinfo.h>` belongs to the UNIX-specific
69  * GIO interfaces, thus you have to use the `gio-unix-2.0.pc` pkg-config
70  * file when using it.
71  */
72
73 #define DEFAULT_APPLICATIONS_GROUP  "Default Applications"
74 #define ADDED_ASSOCIATIONS_GROUP    "Added Associations"
75 #define REMOVED_ASSOCIATIONS_GROUP  "Removed Associations"
76 #define MIME_CACHE_GROUP            "MIME Cache"
77 #define GENERIC_NAME_KEY            "GenericName"
78 #define FULL_NAME_KEY               "X-GNOME-FullName"
79 #define KEYWORDS_KEY                "Keywords"
80 #define STARTUP_WM_CLASS_KEY        "StartupWMClass"
81
82 enum {
83   PROP_0,
84   PROP_FILENAME
85 };
86
87 static void     g_desktop_app_info_iface_init         (GAppInfoIface    *iface);
88 static gboolean g_desktop_app_info_ensure_saved       (GDesktopAppInfo  *info,
89                                                        GError          **error);
90
91 /**
92  * GDesktopAppInfo:
93  *
94  * Information about an installed application from a desktop file.
95  */
96 struct _GDesktopAppInfo
97 {
98   GObject parent_instance;
99
100   char *desktop_id;
101   char *filename;
102   char *app_id;
103
104   GKeyFile *keyfile;
105
106   char *name;
107   char *generic_name;
108   char *fullname;
109   char *comment;
110   char *icon_name;
111   GIcon *icon;
112   char **keywords;
113   char **only_show_in;
114   char **not_show_in;
115   char *try_exec;
116   char *exec;
117   char *binary;
118   char *path;
119   char *categories;
120   char *startup_wm_class;
121   char **mime_types;
122   char **actions;
123
124   guint nodisplay       : 1;
125   guint hidden          : 1;
126   guint terminal        : 1;
127   guint startup_notify  : 1;
128   guint no_fuse         : 1;
129 };
130
131 typedef enum {
132   UPDATE_MIME_NONE = 1 << 0,
133   UPDATE_MIME_SET_DEFAULT = 1 << 1,
134   UPDATE_MIME_SET_NON_DEFAULT = 1 << 2,
135   UPDATE_MIME_REMOVE = 1 << 3,
136   UPDATE_MIME_SET_LAST_USED = 1 << 4,
137 } UpdateMimeFlags;
138
139 G_DEFINE_TYPE_WITH_CODE (GDesktopAppInfo, g_desktop_app_info, G_TYPE_OBJECT,
140                          G_IMPLEMENT_INTERFACE (G_TYPE_APP_INFO, g_desktop_app_info_iface_init))
141
142 /* DesktopFileDir implementation {{{1 */
143
144 typedef struct
145 {
146   gatomicrefcount             ref_count;
147   gchar                      *path;
148   gchar                      *alternatively_watching;
149   gboolean                    is_config;
150   gboolean                    is_setup;
151   GFileMonitor               *monitor;
152   GHashTable                 *app_names;
153   GHashTable                 *mime_tweaks;
154   GHashTable                 *memory_index;
155   GHashTable                 *memory_implementations;
156 } DesktopFileDir;
157
158 static GPtrArray      *desktop_file_dirs = NULL;
159 static const gchar    *desktop_file_dirs_config_dir = NULL;
160 static DesktopFileDir *desktop_file_dir_user_config = NULL;  /* (owned) */
161 static DesktopFileDir *desktop_file_dir_user_data = NULL;  /* (owned) */
162 static GMutex          desktop_file_dir_lock;
163
164 /* Monitor 'changed' signal handler {{{2 */
165 static void desktop_file_dir_reset (DesktopFileDir *dir);
166
167 static DesktopFileDir *
168 desktop_file_dir_ref (DesktopFileDir *dir)
169 {
170   g_atomic_ref_count_inc (&dir->ref_count);
171
172   return dir;
173 }
174
175 static void
176 desktop_file_dir_unref (DesktopFileDir *dir)
177 {
178   if (g_atomic_ref_count_dec (&dir->ref_count))
179     {
180       desktop_file_dir_reset (dir);
181       g_free (dir->path);
182       g_free (dir);
183     }
184 }
185
186 /*< internal >
187  * desktop_file_dir_get_alternative_dir:
188  * @dir: a #DesktopFileDir
189  *
190  * Gets the "alternative" directory to monitor in case the path
191  * doesn't exist.
192  *
193  * If the path exists this will return NULL, otherwise it will return a
194  * parent directory of the path.
195  *
196  * This is used to avoid inotify on a non-existent directory (which
197  * results in polling).
198  *
199  * See https://bugzilla.gnome.org/show_bug.cgi?id=522314 for more info.
200  */
201 static gchar *
202 desktop_file_dir_get_alternative_dir (DesktopFileDir *dir)
203 {
204   gchar *parent;
205
206   /* If the directory itself exists then we need no alternative. */
207   if (g_access (dir->path, R_OK | X_OK) == 0)
208     return NULL;
209
210   /* Otherwise, try the parent directories until we find one. */
211   parent = g_path_get_dirname (dir->path);
212
213   while (g_access (parent, R_OK | X_OK) != 0)
214     {
215       gchar *tmp = parent;
216
217       parent = g_path_get_dirname (tmp);
218
219       /* If somehow we get to '/' or '.' then just stop... */
220       if (g_str_equal (parent, tmp))
221         {
222           g_free (tmp);
223           break;
224         }
225
226       g_free (tmp);
227     }
228
229   return parent;
230 }
231
232 static void
233 desktop_file_dir_changed (GFileMonitor      *monitor,
234                           GFile             *file,
235                           GFile             *other_file,
236                           GFileMonitorEvent  event_type,
237                           gpointer           user_data)
238 {
239   DesktopFileDir *dir = user_data;
240   gboolean do_nothing = FALSE;
241
242   /* We are not interested in receiving notifications forever just
243    * because someone asked about one desktop file once.
244    *
245    * After we receive the first notification, reset the dir, destroying
246    * the monitor.  We will take this as a hint, next time that we are
247    * asked, that we need to check if everything is up to date.
248    *
249    * If this is a notification for a parent directory (because the
250    * desktop directory didn't exist) then we shouldn't fire the signal
251    * unless something actually changed.
252    */
253   g_mutex_lock (&desktop_file_dir_lock);
254
255   if (dir->alternatively_watching)
256     {
257       gchar *alternative_dir;
258
259       alternative_dir = desktop_file_dir_get_alternative_dir (dir);
260       do_nothing = alternative_dir && g_str_equal (dir->alternatively_watching, alternative_dir);
261       g_free (alternative_dir);
262     }
263
264   if (!do_nothing)
265     desktop_file_dir_reset (dir);
266
267   g_mutex_unlock (&desktop_file_dir_lock);
268
269   /* Notify anyone else who may be interested */
270   if (!do_nothing)
271     g_app_info_monitor_fire ();
272 }
273
274 /* Internal utility functions {{{2 */
275
276 /*< internal >
277  * desktop_file_dir_app_name_is_masked:
278  * @dir: a #DesktopFileDir
279  * @app_name: an application ID
280  *
281  * Checks if @app_name is masked for @dir.
282  *
283  * An application is masked if a similarly-named desktop file exists in
284  * a desktop file directory with higher precedence.  Masked desktop
285  * files should be ignored.
286  */
287 static gboolean
288 desktop_file_dir_app_name_is_masked (DesktopFileDir *dir,
289                                      const gchar    *app_name)
290 {
291   guint i;
292
293   for (i = 0; i < desktop_file_dirs->len; i++)
294     {
295       DesktopFileDir *i_dir = g_ptr_array_index (desktop_file_dirs, i);
296
297       if (dir == i_dir)
298         return FALSE;
299       if (i_dir->app_names && g_hash_table_contains (i_dir->app_names, app_name))
300         return TRUE;
301     }
302
303   return FALSE;
304 }
305
306 static const gchar * const *
307 get_lowercase_current_desktops (void)
308 {
309   static gchar **result;
310
311   if (g_once_init_enter (&result))
312     {
313       const gchar *envvar;
314       gchar **tmp;
315
316       envvar = g_getenv ("XDG_CURRENT_DESKTOP");
317
318       if (envvar)
319         {
320           gint i, j;
321
322           tmp = g_strsplit (envvar, G_SEARCHPATH_SEPARATOR_S, 0);
323
324           for (i = 0; tmp[i]; i++)
325             for (j = 0; tmp[i][j]; j++)
326               tmp[i][j] = g_ascii_tolower (tmp[i][j]);
327         }
328       else
329         tmp = g_new0 (gchar *, 0 + 1);
330
331       g_once_init_leave (&result, tmp);
332     }
333
334   return (const gchar **) result;
335 }
336
337 static const gchar * const *
338 get_current_desktops (const gchar *value)
339 {
340   static gchar **result;
341
342   if (g_once_init_enter (&result))
343     {
344       gchar **tmp;
345
346       if (!value)
347         value = g_getenv ("XDG_CURRENT_DESKTOP");
348
349       if (!value)
350         value = "";
351
352       tmp = g_strsplit (value, ":", 0);
353
354       g_once_init_leave (&result, tmp);
355     }
356
357   return (const gchar **) result;
358 }
359
360 /*< internal >
361  * add_to_table_if_appropriate:
362  * @apps: a string to GDesktopAppInfo hash table
363  * @app_name: the name of the application
364  * @info: a #GDesktopAppInfo, or NULL
365  *
366  * If @info is non-%NULL and non-hidden, then add it to @apps, using
367  * @app_name as a key.
368  *
369  * If @info is non-%NULL then this function will consume the passed-in
370  * reference.
371  */
372 static void
373 add_to_table_if_appropriate (GHashTable      *apps,
374                              const gchar     *app_name,
375                              GDesktopAppInfo *info)
376 {
377   if (!info)
378     return;
379
380   if (info->hidden)
381     {
382       g_object_unref (info);
383       return;
384     }
385
386   g_free (info->desktop_id);
387   info->desktop_id = g_strdup (app_name);
388
389   g_hash_table_insert (apps, g_strdup (info->desktop_id), info);
390 }
391
392 enum
393 {
394   DESKTOP_KEY_Comment,
395   DESKTOP_KEY_Exec,
396   DESKTOP_KEY_GenericName,
397   DESKTOP_KEY_Keywords,
398   DESKTOP_KEY_Name,
399   DESKTOP_KEY_X_GNOME_FullName,
400
401   N_DESKTOP_KEYS
402 };
403
404 const gchar desktop_key_match_category[N_DESKTOP_KEYS] = {
405   /* Note: lower numbers are a better match.
406    *
407    * In case we want two keys to match at the same level, we can just
408    * use the same number for the two different keys.
409    */
410   [DESKTOP_KEY_Name]             = 1,
411   [DESKTOP_KEY_Exec]             = 2,
412   [DESKTOP_KEY_Keywords]         = 3,
413   [DESKTOP_KEY_GenericName]      = 4,
414   [DESKTOP_KEY_X_GNOME_FullName] = 5,
415   [DESKTOP_KEY_Comment]          = 6
416 };
417
418 /* Common prefix commands to ignore from Exec= lines */
419 const char * const exec_key_match_blacklist[] = {
420   "bash",
421   "env",
422   "flatpak",
423   "gjs",
424   "pkexec",
425   "python",
426   "python2",
427   "python3",
428   "sh",
429   "wine",
430   "wine64",
431   NULL
432 };
433
434 static gchar *
435 desktop_key_get_name (guint key_id)
436 {
437   switch (key_id)
438     {
439     case DESKTOP_KEY_Comment:
440       return "Comment";
441     case DESKTOP_KEY_Exec:
442       return "Exec";
443     case DESKTOP_KEY_GenericName:
444       return GENERIC_NAME_KEY;
445     case DESKTOP_KEY_Keywords:
446       return KEYWORDS_KEY;
447     case DESKTOP_KEY_Name:
448       return "Name";
449     case DESKTOP_KEY_X_GNOME_FullName:
450       return FULL_NAME_KEY;
451     default:
452       g_assert_not_reached ();
453     }
454 }
455
456 /* Search global state {{{2
457  *
458  * We only ever search under a global lock, so we can use (and reuse)
459  * some global data to reduce allocations made while searching.
460  *
461  * In short, we keep around arrays of results that we expand as needed
462  * (and never shrink).
463  *
464  * static_token_results: this is where we append the results for each
465  *     token within a given desktop directory, as we handle it (which is
466  *     a union of all matches for this term)
467  *
468  * static_search_results: this is where we build the complete results
469  *     for a single directory (which is an intersection of the matches
470  *     found for each term)
471  *
472  * static_total_results: this is where we build the complete results
473  *     across all directories (which is a union of the matches found in
474  *     each directory)
475  *
476  * The app_names that enter these tables are always pointer-unique (in
477  * the sense that string equality is the same as pointer equality).
478  * This can be guaranteed for two reasons:
479  *
480  *   - we mask appids so that a given appid will only ever appear within
481  *     the highest-precedence directory that contains it.  We never
482  *     return search results from a lower-level directory if a desktop
483  *     file exists in a higher-level one.
484  *
485  *   - within a given directory, the string is unique because it's the
486  *     key in the hashtable of all app_ids for that directory.
487  *
488  * We perform a merging of the results in merge_token_results().  This
489  * works by ordering the two lists and moving through each of them (at
490  * the same time) looking for common elements, rejecting uncommon ones.
491  * "Order" here need not mean any particular thing, as long as it is
492  * some order.  Because of the uniqueness of our strings, we can use
493  * pointer order.  That's what's going on in compare_results() below.
494  */
495 struct search_result
496 {
497   const gchar *app_name;
498   gint         category;
499 };
500
501 static struct search_result *static_token_results;
502 static gint                  static_token_results_size;
503 static gint                  static_token_results_allocated;
504 static struct search_result *static_search_results;
505 static gint                  static_search_results_size;
506 static gint                  static_search_results_allocated;
507 static struct search_result *static_total_results;
508 static gint                  static_total_results_size;
509 static gint                  static_total_results_allocated;
510
511 /* And some functions for performing nice operations against it */
512 static gint
513 compare_results (gconstpointer a,
514                  gconstpointer b)
515 {
516   const struct search_result *ra = a;
517   const struct search_result *rb = b;
518
519   if (ra->app_name < rb->app_name)
520     return -1;
521
522   else if (ra->app_name > rb->app_name)
523     return 1;
524
525   else
526     return ra->category - rb->category;
527 }
528
529 static gint
530 compare_categories (gconstpointer a,
531                     gconstpointer b)
532 {
533   const struct search_result *ra = a;
534   const struct search_result *rb = b;
535
536   return ra->category - rb->category;
537 }
538
539 static void
540 add_token_result (const gchar *app_name,
541                   guint16      category)
542 {
543   if G_UNLIKELY (static_token_results_size == static_token_results_allocated)
544     {
545       static_token_results_allocated = MAX (16, static_token_results_allocated * 2);
546       static_token_results = g_renew (struct search_result, static_token_results, static_token_results_allocated);
547     }
548
549   static_token_results[static_token_results_size].app_name = app_name;
550   static_token_results[static_token_results_size].category = category;
551   static_token_results_size++;
552 }
553
554 static void
555 merge_token_results (gboolean first)
556 {
557   if (static_token_results_size != 0)
558     qsort (static_token_results, static_token_results_size, sizeof (struct search_result), compare_results);
559
560   /* If this is the first token then we are basically merging a list with
561    * itself -- we only perform de-duplication.
562    *
563    * If this is not the first token then we are doing a real merge.
564    */
565   if (first)
566     {
567       const gchar *last_name = NULL;
568       gint i;
569
570       /* We must de-duplicate, but we do so by taking the best category
571        * in each case.
572        *
573        * The final list can be as large as the input here, so make sure
574        * we have enough room (even if it's too much room).
575        */
576
577       if G_UNLIKELY (static_search_results_allocated < static_token_results_size)
578         {
579           static_search_results_allocated = static_token_results_allocated;
580           static_search_results = g_renew (struct search_result,
581                                            static_search_results,
582                                            static_search_results_allocated);
583         }
584
585       for (i = 0; i < static_token_results_size; i++)
586         {
587           /* The list is sorted so that the best match for a given id
588            * will be at the front, so once we have copied an id, skip
589            * the rest of the entries for the same id.
590            */
591           if (static_token_results[i].app_name == last_name)
592             continue;
593
594           last_name = static_token_results[i].app_name;
595
596           static_search_results[static_search_results_size++] = static_token_results[i];
597         }
598     }
599   else
600     {
601       const gchar *last_name = NULL;
602       gint i, j = 0;
603       gint k = 0;
604
605       /* We only ever remove items from the results list, so no need to
606        * resize to ensure that we have enough room.
607        */
608       for (i = 0; i < static_token_results_size; i++)
609         {
610           if (static_token_results[i].app_name == last_name)
611             continue;
612
613           last_name = static_token_results[i].app_name;
614
615           /* Now we only want to have a result in static_search_results
616            * if we already have it there *and* we have it in
617            * static_token_results as well.  The category will be the
618            * lesser of the two.
619            *
620            * Skip past the results in static_search_results that are not
621            * going to be matches.
622            */
623           while (k < static_search_results_size &&
624                  static_search_results[k].app_name < static_token_results[i].app_name)
625             k++;
626
627           if (k < static_search_results_size &&
628               static_search_results[k].app_name == static_token_results[i].app_name)
629             {
630               /* We have a match.
631                *
632                * Category should be the worse of the two (ie:
633                * numerically larger).
634                */
635               static_search_results[j].app_name = static_search_results[k].app_name;
636               static_search_results[j].category = MAX (static_search_results[k].category,
637                                                        static_token_results[i].category);
638               j++;
639             }
640         }
641
642       static_search_results_size = j;
643     }
644
645   /* Clear it out for next time... */
646   static_token_results_size = 0;
647 }
648
649 static void
650 reset_total_search_results (void)
651 {
652   static_total_results_size = 0;
653 }
654
655 static void
656 sort_total_search_results (void)
657 {
658   if (static_total_results_size != 0)
659     qsort (static_total_results, static_total_results_size, sizeof (struct search_result), compare_categories);
660 }
661
662 static void
663 merge_directory_results (void)
664 {
665   if G_UNLIKELY (static_total_results_size + static_search_results_size > static_total_results_allocated)
666     {
667       static_total_results_allocated = MAX (16, static_total_results_allocated);
668       while (static_total_results_allocated < static_total_results_size + static_search_results_size)
669         static_total_results_allocated *= 2;
670       static_total_results = g_renew (struct search_result, static_total_results, static_total_results_allocated);
671     }
672
673   if (static_total_results + static_total_results_size != 0)
674     memcpy (static_total_results + static_total_results_size,
675             static_search_results,
676             static_search_results_size * sizeof (struct search_result));
677
678   static_total_results_size += static_search_results_size;
679
680   /* Clear it out for next time... */
681   static_search_results_size = 0;
682 }
683
684 /* Support for unindexed DesktopFileDirs {{{2 */
685 static void
686 get_apps_from_dir (GHashTable **apps,
687                    const char  *dirname,
688                    const char  *prefix)
689 {
690   const char *basename;
691   GDir *dir;
692
693   dir = g_dir_open (dirname, 0, NULL);
694
695   if (dir == NULL)
696     return;
697
698   while ((basename = g_dir_read_name (dir)) != NULL)
699     {
700       gchar *filename;
701
702       filename = g_build_filename (dirname, basename, NULL);
703
704       if (g_str_has_suffix (basename, ".desktop"))
705         {
706           gchar *app_name;
707
708           app_name = g_strconcat (prefix, basename, NULL);
709
710           if (*apps == NULL)
711             *apps = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
712
713           g_hash_table_insert (*apps, app_name, g_strdup (filename));
714         }
715       else if (g_file_test (filename, G_FILE_TEST_IS_DIR))
716         {
717           gchar *subprefix;
718
719           subprefix = g_strconcat (prefix, basename, "-", NULL);
720           get_apps_from_dir (apps, filename, subprefix);
721           g_free (subprefix);
722         }
723
724       g_free (filename);
725     }
726
727   g_dir_close (dir);
728 }
729
730 typedef struct
731 {
732   gchar **additions;
733   gchar **removals;
734   gchar **defaults;
735 } UnindexedMimeTweaks;
736
737 static void
738 free_mime_tweaks (gpointer data)
739 {
740   UnindexedMimeTweaks *tweaks = data;
741
742   g_strfreev (tweaks->additions);
743   g_strfreev (tweaks->removals);
744   g_strfreev (tweaks->defaults);
745
746   g_slice_free (UnindexedMimeTweaks, tweaks);
747 }
748
749 static UnindexedMimeTweaks *
750 desktop_file_dir_unindexed_get_tweaks (DesktopFileDir *dir,
751                                        const gchar    *mime_type)
752 {
753   UnindexedMimeTweaks *tweaks;
754   gchar *unaliased_type;
755
756   unaliased_type = _g_unix_content_type_unalias (mime_type);
757   tweaks = g_hash_table_lookup (dir->mime_tweaks, unaliased_type);
758
759   if (tweaks == NULL)
760     {
761       tweaks = g_slice_new0 (UnindexedMimeTweaks);
762       g_hash_table_insert (dir->mime_tweaks, unaliased_type, tweaks);
763     }
764   else
765     g_free (unaliased_type);
766
767   return tweaks;
768 }
769
770 /* consumes 'to_add' */
771 static void
772 expand_strv (gchar         ***strv_ptr,
773              gchar          **to_add,
774              gchar * const   *blacklist)
775 {
776   guint strv_len, add_len;
777   gchar **strv;
778   guint i, j;
779
780   if (!*strv_ptr)
781     {
782       *strv_ptr = to_add;
783       return;
784     }
785
786   strv = *strv_ptr;
787   strv_len = g_strv_length (strv);
788   add_len = g_strv_length (to_add);
789   strv = g_renew (gchar *, strv, strv_len + add_len + 1);
790
791   for (i = 0; to_add[i]; i++)
792     {
793       /* Don't add blacklisted strings */
794       if (blacklist)
795         for (j = 0; blacklist[j]; j++)
796           if (g_str_equal (to_add[i], blacklist[j]))
797             goto no_add;
798
799       /* Don't add duplicates already in the list */
800       for (j = 0; j < strv_len; j++)
801         if (g_str_equal (to_add[i], strv[j]))
802           goto no_add;
803
804       strv[strv_len++] = to_add[i];
805       continue;
806
807 no_add:
808       g_free (to_add[i]);
809     }
810
811   strv[strv_len] = NULL;
812   *strv_ptr = strv;
813
814   g_free (to_add);
815 }
816
817 static void
818 desktop_file_dir_unindexed_read_mimeapps_list (DesktopFileDir *dir,
819                                                const gchar    *filename,
820                                                const gchar    *added_group,
821                                                gboolean        tweaks_permitted)
822 {
823   UnindexedMimeTweaks *tweaks;
824   char **desktop_file_ids;
825   GKeyFile *key_file;
826   gchar **mime_types;
827   int i;
828
829   key_file = g_key_file_new ();
830   if (!g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, NULL))
831     {
832       g_key_file_free (key_file);
833       return;
834     }
835
836   mime_types = g_key_file_get_keys (key_file, added_group, NULL, NULL);
837
838   if G_UNLIKELY (mime_types != NULL && !tweaks_permitted)
839     {
840       g_warning ("%s contains a [%s] group, but it is not permitted here.  Only the non-desktop-specific "
841                  "mimeapps.list file may add or remove associations.", filename, added_group);
842       g_strfreev (mime_types);
843       mime_types = NULL;
844     }
845
846   if (mime_types != NULL)
847     {
848       for (i = 0; mime_types[i] != NULL; i++)
849         {
850           desktop_file_ids = g_key_file_get_string_list (key_file, added_group, mime_types[i], NULL, NULL);
851
852           if (desktop_file_ids)
853             {
854               tweaks = desktop_file_dir_unindexed_get_tweaks (dir, mime_types[i]);
855               expand_strv (&tweaks->additions, desktop_file_ids, tweaks->removals);
856             }
857         }
858
859       g_strfreev (mime_types);
860     }
861
862   mime_types = g_key_file_get_keys (key_file, REMOVED_ASSOCIATIONS_GROUP, NULL, NULL);
863
864   if G_UNLIKELY (mime_types != NULL && !tweaks_permitted)
865     {
866       g_warning ("%s contains a [%s] group, but it is not permitted here.  Only the non-desktop-specific "
867                  "mimeapps.list file may add or remove associations.", filename, REMOVED_ASSOCIATIONS_GROUP);
868       g_strfreev (mime_types);
869       mime_types = NULL;
870     }
871
872   if (mime_types != NULL)
873     {
874       for (i = 0; mime_types[i] != NULL; i++)
875         {
876           desktop_file_ids = g_key_file_get_string_list (key_file, REMOVED_ASSOCIATIONS_GROUP, mime_types[i], NULL, NULL);
877
878           if (desktop_file_ids)
879             {
880               tweaks = desktop_file_dir_unindexed_get_tweaks (dir, mime_types[i]);
881               expand_strv (&tweaks->removals, desktop_file_ids, tweaks->additions);
882             }
883         }
884
885       g_strfreev (mime_types);
886     }
887
888   mime_types = g_key_file_get_keys (key_file, DEFAULT_APPLICATIONS_GROUP, NULL, NULL);
889
890   if (mime_types != NULL)
891     {
892       for (i = 0; mime_types[i] != NULL; i++)
893         {
894           desktop_file_ids = g_key_file_get_string_list (key_file, DEFAULT_APPLICATIONS_GROUP, mime_types[i], NULL, NULL);
895
896           if (desktop_file_ids)
897             {
898               tweaks = desktop_file_dir_unindexed_get_tweaks (dir, mime_types[i]);
899               expand_strv (&tweaks->defaults, desktop_file_ids, NULL);
900             }
901         }
902
903       g_strfreev (mime_types);
904     }
905
906   g_key_file_free (key_file);
907 }
908
909 static void
910 desktop_file_dir_unindexed_read_mimeapps_lists (DesktopFileDir *dir)
911 {
912   const gchar * const *desktops;
913   gchar *filename;
914   gint i;
915
916   dir->mime_tweaks = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, free_mime_tweaks);
917
918   /* We process in order of precedence, using a blacklisting approach to
919    * avoid recording later instructions that conflict with ones we found
920    * earlier.
921    *
922    * We first start with the XDG_CURRENT_DESKTOP files, in precedence
923    * order.
924    */
925   desktops = get_lowercase_current_desktops ();
926   for (i = 0; desktops[i]; i++)
927     {
928       filename = g_strdup_printf ("%s/%s-mimeapps.list", dir->path, desktops[i]);
929       desktop_file_dir_unindexed_read_mimeapps_list (dir, filename, ADDED_ASSOCIATIONS_GROUP, FALSE);
930       g_free (filename);
931     }
932
933   /* Next, the non-desktop-specific mimeapps.list */
934   filename = g_strdup_printf ("%s/mimeapps.list", dir->path);
935   desktop_file_dir_unindexed_read_mimeapps_list (dir, filename, ADDED_ASSOCIATIONS_GROUP, TRUE);
936   g_free (filename);
937
938   /* The remaining files are only checked for in directories that might
939    * contain desktop files (ie: not the config dirs).
940    */
941   if (dir->is_config)
942     return;
943
944   /* We have 'defaults.list' which was only ever understood by GLib.  It
945    * exists widely, but it has never been part of any spec and it should
946    * be treated as deprecated.  This will be removed in a future
947    * version.
948    */
949   filename = g_strdup_printf ("%s/defaults.list", dir->path);
950   desktop_file_dir_unindexed_read_mimeapps_list (dir, filename, ADDED_ASSOCIATIONS_GROUP, FALSE);
951   g_free (filename);
952
953   /* Finally, the mimeinfo.cache, which is just a cached copy of what we
954    * would find in the MimeTypes= lines of all of the desktop files.
955    */
956   filename = g_strdup_printf ("%s/mimeinfo.cache", dir->path);
957   desktop_file_dir_unindexed_read_mimeapps_list (dir, filename, MIME_CACHE_GROUP, TRUE);
958   g_free (filename);
959 }
960
961 static void
962 desktop_file_dir_unindexed_init (DesktopFileDir *dir)
963 {
964   if (!dir->is_config)
965     get_apps_from_dir (&dir->app_names, dir->path, "");
966
967   desktop_file_dir_unindexed_read_mimeapps_lists (dir);
968 }
969
970 static GDesktopAppInfo *
971 desktop_file_dir_unindexed_get_app (DesktopFileDir *dir,
972                                     const gchar    *desktop_id)
973 {
974   const gchar *filename;
975
976   filename = g_hash_table_lookup (dir->app_names, desktop_id);
977
978   if (!filename)
979     return NULL;
980
981   return g_desktop_app_info_new_from_filename (filename);
982 }
983
984 static void
985 desktop_file_dir_unindexed_get_all (DesktopFileDir *dir,
986                                     GHashTable     *apps)
987 {
988   GHashTableIter iter;
989   gpointer app_name;
990   gpointer filename;
991
992   if (dir->app_names == NULL)
993     return;
994
995   g_hash_table_iter_init (&iter, dir->app_names);
996   while (g_hash_table_iter_next (&iter, &app_name, &filename))
997     {
998       if (desktop_file_dir_app_name_is_masked (dir, app_name))
999         continue;
1000
1001       add_to_table_if_appropriate (apps, app_name, g_desktop_app_info_new_from_filename (filename));
1002     }
1003 }
1004
1005 typedef struct _MemoryIndexEntry MemoryIndexEntry;
1006 typedef GHashTable MemoryIndex;
1007
1008 struct _MemoryIndexEntry
1009 {
1010   const gchar      *app_name; /* pointer to the hashtable key */
1011   gint              match_category;
1012   MemoryIndexEntry *next;
1013 };
1014
1015 static void
1016 memory_index_entry_free (gpointer data)
1017 {
1018   MemoryIndexEntry *mie = data;
1019
1020   while (mie)
1021     {
1022       MemoryIndexEntry *next = mie->next;
1023
1024       g_slice_free (MemoryIndexEntry, mie);
1025       mie = next;
1026     }
1027 }
1028
1029 static void
1030 memory_index_add_token (MemoryIndex *mi,
1031                         const gchar *token,
1032                         gint         match_category,
1033                         const gchar *app_name)
1034 {
1035   MemoryIndexEntry *mie, *first;
1036
1037   mie = g_slice_new (MemoryIndexEntry);
1038   mie->app_name = app_name;
1039   mie->match_category = match_category;
1040
1041   first = g_hash_table_lookup (mi, token);
1042
1043   if (first)
1044     {
1045       mie->next = first->next;
1046       first->next = mie;
1047     }
1048   else
1049     {
1050       mie->next = NULL;
1051       g_hash_table_insert (mi, g_strdup (token), mie);
1052     }
1053 }
1054
1055 static void
1056 memory_index_add_string (MemoryIndex *mi,
1057                          const gchar *string,
1058                          gint         match_category,
1059                          const gchar *app_name)
1060 {
1061   gchar **tokens, **alternates;
1062   gint i;
1063
1064   tokens = g_str_tokenize_and_fold (string, NULL, &alternates);
1065
1066   for (i = 0; tokens[i]; i++)
1067     memory_index_add_token (mi, tokens[i], match_category, app_name);
1068
1069   for (i = 0; alternates[i]; i++)
1070     memory_index_add_token (mi, alternates[i], match_category, app_name);
1071
1072   g_strfreev (alternates);
1073   g_strfreev (tokens);
1074 }
1075
1076 static MemoryIndex *
1077 memory_index_new (void)
1078 {
1079   return g_hash_table_new_full (g_str_hash, g_str_equal, g_free, memory_index_entry_free);
1080 }
1081
1082 static void
1083 desktop_file_dir_unindexed_setup_search (DesktopFileDir *dir)
1084 {
1085   GHashTableIter iter;
1086   gpointer app, path;
1087
1088   dir->memory_index = memory_index_new ();
1089   dir->memory_implementations = memory_index_new ();
1090
1091   /* Nothing to search? */
1092   if (dir->app_names == NULL)
1093     return;
1094
1095   g_hash_table_iter_init (&iter, dir->app_names);
1096   while (g_hash_table_iter_next (&iter, &app, &path))
1097     {
1098       GKeyFile *key_file;
1099
1100       if (desktop_file_dir_app_name_is_masked (dir, app))
1101         continue;
1102
1103       key_file = g_key_file_new ();
1104
1105       if (g_key_file_load_from_file (key_file, path, G_KEY_FILE_NONE, NULL) &&
1106           !g_key_file_get_boolean (key_file, "Desktop Entry", "Hidden", NULL))
1107         {
1108           /* Index the interesting keys... */
1109           gchar **implements;
1110           gint i;
1111
1112           for (i = 0; i < G_N_ELEMENTS (desktop_key_match_category); i++)
1113             {
1114               const gchar *value;
1115               gchar *raw;
1116
1117               if (!desktop_key_match_category[i])
1118                 continue;
1119
1120               raw = g_key_file_get_locale_string (key_file, "Desktop Entry", desktop_key_get_name (i), NULL, NULL);
1121               value = raw;
1122
1123               if (i == DESKTOP_KEY_Exec && raw != NULL)
1124                 {
1125                   /* Special handling: only match basename of first field */
1126                   gchar *space;
1127                   gchar *slash;
1128
1129                   /* Remove extra arguments, if any */
1130                   space = raw + strcspn (raw, " \t\n"); /* IFS */
1131                   *space = '\0';
1132
1133                   /* Skip the pathname, if any */
1134                   if ((slash = strrchr (raw, '/')))
1135                     value = slash + 1;
1136
1137                   /* Don't match on blacklisted binaries like interpreters */
1138                   if (g_strv_contains (exec_key_match_blacklist, value))
1139                     value = NULL;
1140                 }
1141
1142               if (value)
1143                 memory_index_add_string (dir->memory_index, value, desktop_key_match_category[i], app);
1144
1145               g_free (raw);
1146             }
1147
1148           /* Make note of the Implements= line */
1149           implements = g_key_file_get_string_list (key_file, "Desktop Entry", "Implements", NULL, NULL);
1150           for (i = 0; implements && implements[i]; i++)
1151             memory_index_add_token (dir->memory_implementations, implements[i], 0, app);
1152           g_strfreev (implements);
1153         }
1154
1155       g_key_file_free (key_file);
1156     }
1157 }
1158
1159 static void
1160 desktop_file_dir_unindexed_search (DesktopFileDir  *dir,
1161                                    const gchar     *search_token)
1162 {
1163   GHashTableIter iter;
1164   gpointer key, value;
1165
1166   if (!dir->memory_index)
1167     desktop_file_dir_unindexed_setup_search (dir);
1168
1169   g_hash_table_iter_init (&iter, dir->memory_index);
1170   while (g_hash_table_iter_next (&iter, &key, &value))
1171     {
1172       MemoryIndexEntry *mie = value;
1173
1174       if (!g_str_has_prefix (key, search_token))
1175         continue;
1176
1177       while (mie)
1178         {
1179           add_token_result (mie->app_name, mie->match_category);
1180           mie = mie->next;
1181         }
1182     }
1183 }
1184
1185 static gboolean
1186 array_contains (GPtrArray *array,
1187                 const gchar *str)
1188 {
1189   gint i;
1190
1191   for (i = 0; i < array->len; i++)
1192     if (g_str_equal (array->pdata[i], str))
1193       return TRUE;
1194
1195   return FALSE;
1196 }
1197
1198 static void
1199 desktop_file_dir_unindexed_mime_lookup (DesktopFileDir *dir,
1200                                         const gchar    *mime_type,
1201                                         GPtrArray      *hits,
1202                                         GPtrArray      *blacklist)
1203 {
1204   UnindexedMimeTweaks *tweaks;
1205   gint i;
1206
1207   tweaks = g_hash_table_lookup (dir->mime_tweaks, mime_type);
1208
1209   if (!tweaks)
1210     return;
1211
1212   if (tweaks->additions)
1213     {
1214       for (i = 0; tweaks->additions[i]; i++)
1215         {
1216           gchar *app_name = tweaks->additions[i];
1217
1218           if (!desktop_file_dir_app_name_is_masked (dir, app_name) &&
1219               !array_contains (blacklist, app_name) && !array_contains (hits, app_name))
1220             g_ptr_array_add (hits, app_name);
1221         }
1222     }
1223
1224   if (tweaks->removals)
1225     {
1226       for (i = 0; tweaks->removals[i]; i++)
1227         {
1228           gchar *app_name = tweaks->removals[i];
1229
1230           if (!desktop_file_dir_app_name_is_masked (dir, app_name) &&
1231               !array_contains (blacklist, app_name) && !array_contains (hits, app_name))
1232             g_ptr_array_add (blacklist, app_name);
1233         }
1234     }
1235 }
1236
1237 static void
1238 desktop_file_dir_unindexed_default_lookup (DesktopFileDir *dir,
1239                                            const gchar    *mime_type,
1240                                            GPtrArray      *results)
1241 {
1242   UnindexedMimeTweaks *tweaks;
1243   gint i;
1244
1245   tweaks = g_hash_table_lookup (dir->mime_tweaks, mime_type);
1246
1247   if (!tweaks || !tweaks->defaults)
1248     return;
1249
1250   for (i = 0; tweaks->defaults[i]; i++)
1251     {
1252       gchar *app_name = tweaks->defaults[i];
1253
1254       if (!array_contains (results, app_name))
1255         g_ptr_array_add (results, app_name);
1256     }
1257 }
1258
1259 static void
1260 desktop_file_dir_unindexed_get_implementations (DesktopFileDir  *dir,
1261                                                 GList          **results,
1262                                                 const gchar     *interface)
1263 {
1264   MemoryIndexEntry *mie;
1265
1266   if (!dir->memory_index)
1267     desktop_file_dir_unindexed_setup_search (dir);
1268
1269   for (mie = g_hash_table_lookup (dir->memory_implementations, interface); mie; mie = mie->next)
1270     *results = g_list_prepend (*results, g_strdup (mie->app_name));
1271 }
1272
1273 /* DesktopFileDir "API" {{{2 */
1274
1275 /*< internal >
1276  * desktop_file_dir_new:
1277  * @data_dir: an XDG_DATA_DIR
1278  *
1279  * Creates a #DesktopFileDir for the corresponding @data_dir.
1280  */
1281 static DesktopFileDir *
1282 desktop_file_dir_new (const gchar *data_dir)
1283 {
1284   DesktopFileDir *dir = g_new0 (DesktopFileDir, 1);
1285
1286   g_atomic_ref_count_init (&dir->ref_count);
1287   dir->path = g_build_filename (data_dir, "applications", NULL);
1288
1289   return g_steal_pointer (&dir);
1290 }
1291
1292 /*< internal >
1293  * desktop_file_dir_new_for_config:
1294  * @config_dir: an XDG_CONFIG_DIR
1295  *
1296  * Just the same as desktop_file_dir_new() except that it does not
1297  * add the "applications" directory.  It also marks the directory as
1298  * config-only, which prevents us from attempting to find desktop files
1299  * here.
1300  */
1301 static DesktopFileDir *
1302 desktop_file_dir_new_for_config (const gchar *config_dir)
1303 {
1304   DesktopFileDir *dir = g_new0 (DesktopFileDir, 1);
1305
1306   g_atomic_ref_count_init (&dir->ref_count);
1307   dir->path = g_strdup (config_dir);
1308   dir->is_config = TRUE;
1309
1310   return g_steal_pointer (&dir);
1311 }
1312
1313 /*< internal >
1314  * desktop_file_dir_reset:
1315  * @dir: a #DesktopFileDir
1316  *
1317  * Cleans up @dir, releasing most resources that it was using.
1318  */
1319 static void
1320 desktop_file_dir_reset (DesktopFileDir *dir)
1321 {
1322   if (dir->alternatively_watching)
1323     {
1324       g_free (dir->alternatively_watching);
1325       dir->alternatively_watching = NULL;
1326     }
1327
1328   if (dir->monitor)
1329     {
1330       g_signal_handlers_disconnect_by_func (dir->monitor, desktop_file_dir_changed, dir);
1331       g_file_monitor_cancel (dir->monitor);
1332       g_object_unref (dir->monitor);
1333       dir->monitor = NULL;
1334     }
1335
1336   if (dir->app_names)
1337     {
1338       g_hash_table_unref (dir->app_names);
1339       dir->app_names = NULL;
1340     }
1341
1342   if (dir->memory_index)
1343     {
1344       g_hash_table_unref (dir->memory_index);
1345       dir->memory_index = NULL;
1346     }
1347
1348   if (dir->mime_tweaks)
1349     {
1350       g_hash_table_unref (dir->mime_tweaks);
1351       dir->mime_tweaks = NULL;
1352     }
1353
1354   if (dir->memory_implementations)
1355     {
1356       g_hash_table_unref (dir->memory_implementations);
1357       dir->memory_implementations = NULL;
1358     }
1359
1360   dir->is_setup = FALSE;
1361 }
1362
1363 static void
1364 closure_notify_cb (gpointer  data,
1365                    GClosure *closure)
1366 {
1367   DesktopFileDir *dir = data;
1368   desktop_file_dir_unref (dir);
1369 }
1370
1371 /*< internal >
1372  * desktop_file_dir_init:
1373  * @dir: a #DesktopFileDir
1374  *
1375  * Does initial setup for @dir
1376  *
1377  * You should only call this if @dir is not already setup.
1378  */
1379 static void
1380 desktop_file_dir_init (DesktopFileDir *dir)
1381 {
1382   const gchar *watch_dir;
1383
1384   g_assert (!dir->is_setup);
1385
1386   g_assert (!dir->alternatively_watching);
1387   g_assert (!dir->monitor);
1388
1389   dir->alternatively_watching = desktop_file_dir_get_alternative_dir (dir);
1390   watch_dir = dir->alternatively_watching ? dir->alternatively_watching : dir->path;
1391
1392   /* There is a very thin race here if the watch_dir has been _removed_
1393    * between when we checked for it and when we establish the watch.
1394    * Removes probably don't happen in usual operation, and even if it
1395    * does (and we catch the unlikely race), the only degradation is that
1396    * we will fall back to polling.
1397    */
1398   dir->monitor = g_local_file_monitor_new_in_worker (watch_dir, TRUE, G_FILE_MONITOR_NONE,
1399                                                      desktop_file_dir_changed,
1400                                                      desktop_file_dir_ref (dir),
1401                                                      closure_notify_cb, NULL);
1402
1403   desktop_file_dir_unindexed_init (dir);
1404
1405   dir->is_setup = TRUE;
1406 }
1407
1408 /*< internal >
1409  * desktop_file_dir_get_app:
1410  * @dir: a DesktopFileDir
1411  * @desktop_id: the desktop ID to load
1412  *
1413  * Creates the #GDesktopAppInfo for the given @desktop_id if it exists
1414  * within @dir, even if it is hidden.
1415  *
1416  * This function does not check if @desktop_id would be masked by a
1417  * directory with higher precedence.  The caller must do so.
1418  */
1419 static GDesktopAppInfo *
1420 desktop_file_dir_get_app (DesktopFileDir *dir,
1421                           const gchar    *desktop_id)
1422 {
1423   if (!dir->app_names)
1424     return NULL;
1425
1426   return desktop_file_dir_unindexed_get_app (dir, desktop_id);
1427 }
1428
1429 /*< internal >
1430  * desktop_file_dir_get_all:
1431  * @dir: a DesktopFileDir
1432  * @apps: a #GHashTable<string, GDesktopAppInfo>
1433  *
1434  * Loads all desktop files in @dir and adds them to @apps, careful to
1435  * ensure we don't add any files masked by a similarly-named file in a
1436  * higher-precedence directory.
1437  */
1438 static void
1439 desktop_file_dir_get_all (DesktopFileDir *dir,
1440                           GHashTable     *apps)
1441 {
1442   desktop_file_dir_unindexed_get_all (dir, apps);
1443 }
1444
1445 /*< internal >
1446  * desktop_file_dir_mime_lookup:
1447  * @dir: a #DesktopFileDir
1448  * @mime_type: the mime type to look up
1449  * @hits: the array to store the hits
1450  * @blacklist: the array to store the blacklist
1451  *
1452  * Does a lookup of a mimetype against one desktop file directory,
1453  * recording any hits and blacklisting and "Removed" associations (so
1454  * later directories don't record them as hits).
1455  *
1456  * The items added to @hits are duplicated, but the ones in @blacklist
1457  * are weak pointers.  This facilitates simply freeing the blacklist
1458  * (which is only used for internal bookkeeping) but using the pdata of
1459  * @hits as the result of the operation.
1460  */
1461 static void
1462 desktop_file_dir_mime_lookup (DesktopFileDir *dir,
1463                               const gchar    *mime_type,
1464                               GPtrArray      *hits,
1465                               GPtrArray      *blacklist)
1466 {
1467   desktop_file_dir_unindexed_mime_lookup (dir, mime_type, hits, blacklist);
1468 }
1469
1470 /*< internal >
1471  * desktop_file_dir_default_lookup:
1472  * @dir: a #DesktopFileDir
1473  * @mime_type: the mime type to look up
1474  * @results: an array to store the results in
1475  *
1476  * Collects the "default" applications for a given mime type from @dir.
1477  */
1478 static void
1479 desktop_file_dir_default_lookup (DesktopFileDir *dir,
1480                                  const gchar    *mime_type,
1481                                  GPtrArray      *results)
1482 {
1483   desktop_file_dir_unindexed_default_lookup (dir, mime_type, results);
1484 }
1485
1486 /*< internal >
1487  * desktop_file_dir_search:
1488  * @dir: a #DesktopFileDir
1489  * @term: a normalised and casefolded search term
1490  *
1491  * Finds the names of applications in @dir that match @term.
1492  */
1493 static void
1494 desktop_file_dir_search (DesktopFileDir *dir,
1495                          const gchar    *search_token)
1496 {
1497   desktop_file_dir_unindexed_search (dir, search_token);
1498 }
1499
1500 static void
1501 desktop_file_dir_get_implementations (DesktopFileDir  *dir,
1502                                       GList          **results,
1503                                       const gchar     *interface)
1504 {
1505   desktop_file_dir_unindexed_get_implementations (dir, results, interface);
1506 }
1507
1508 /* Lock/unlock and global setup API {{{2 */
1509
1510 static void
1511 desktop_file_dirs_lock (void)
1512 {
1513   gint i;
1514   const gchar *user_config_dir = g_get_user_config_dir ();
1515
1516   g_mutex_lock (&desktop_file_dir_lock);
1517
1518   /* If the XDG dirs configuration has changed (expected only during tests),
1519    * clear and reload the state. */
1520   if (desktop_file_dirs_config_dir != NULL &&
1521       g_strcmp0 (desktop_file_dirs_config_dir, user_config_dir) != 0)
1522     {
1523       g_debug ("%s: Resetting desktop app info dirs from %s to %s",
1524                G_STRFUNC, desktop_file_dirs_config_dir, user_config_dir);
1525
1526       g_ptr_array_set_size (desktop_file_dirs, 0);
1527       g_clear_pointer (&desktop_file_dir_user_config, desktop_file_dir_unref);
1528       g_clear_pointer (&desktop_file_dir_user_data, desktop_file_dir_unref);
1529     }
1530
1531   if (desktop_file_dirs == NULL || desktop_file_dirs->len == 0)
1532     {
1533       const char * const *dirs;
1534       gint i;
1535
1536       if (desktop_file_dirs == NULL)
1537         desktop_file_dirs = g_ptr_array_new_with_free_func ((GDestroyNotify) desktop_file_dir_unref);
1538
1539       /* First, the configs.  Highest priority: the user's ~/.config */
1540       desktop_file_dir_user_config = desktop_file_dir_new_for_config (user_config_dir);
1541       g_ptr_array_add (desktop_file_dirs, desktop_file_dir_ref (desktop_file_dir_user_config));
1542
1543       /* Next, the system configs (/etc/xdg, and so on). */
1544       dirs = g_get_system_config_dirs ();
1545       for (i = 0; dirs[i]; i++)
1546         g_ptr_array_add (desktop_file_dirs, desktop_file_dir_new_for_config (dirs[i]));
1547
1548       /* Now the data.  Highest priority: the user's ~/.local/share/applications */
1549       desktop_file_dir_user_data = desktop_file_dir_new (g_get_user_data_dir ());
1550       g_ptr_array_add (desktop_file_dirs, desktop_file_dir_ref (desktop_file_dir_user_data));
1551
1552       /* Following that, XDG_DATA_DIRS/applications, in order */
1553       dirs = g_get_system_data_dirs ();
1554       for (i = 0; dirs[i]; i++)
1555         g_ptr_array_add (desktop_file_dirs, desktop_file_dir_new (dirs[i]));
1556
1557       /* The list of directories will never change after this, unless
1558        * g_get_user_config_dir() changes due to %G_TEST_OPTION_ISOLATE_DIRS. */
1559       desktop_file_dirs_config_dir = user_config_dir;
1560     }
1561
1562   for (i = 0; i < desktop_file_dirs->len; i++)
1563     if (!((DesktopFileDir *) g_ptr_array_index (desktop_file_dirs, i))->is_setup)
1564       desktop_file_dir_init (g_ptr_array_index (desktop_file_dirs, i));
1565 }
1566
1567 static void
1568 desktop_file_dirs_unlock (void)
1569 {
1570   g_mutex_unlock (&desktop_file_dir_lock);
1571 }
1572
1573 static void
1574 desktop_file_dirs_invalidate_user_config (void)
1575 {
1576   g_mutex_lock (&desktop_file_dir_lock);
1577
1578   if (desktop_file_dir_user_config != NULL)
1579     desktop_file_dir_reset (desktop_file_dir_user_config);
1580
1581   g_mutex_unlock (&desktop_file_dir_lock);
1582 }
1583
1584 static void
1585 desktop_file_dirs_invalidate_user_data (void)
1586 {
1587   g_mutex_lock (&desktop_file_dir_lock);
1588
1589   if (desktop_file_dir_user_data != NULL)
1590     desktop_file_dir_reset (desktop_file_dir_user_data);
1591
1592   g_mutex_unlock (&desktop_file_dir_lock);
1593 }
1594
1595 /* GDesktopAppInfo implementation {{{1 */
1596 /* GObject implementation {{{2 */
1597 static void
1598 g_desktop_app_info_finalize (GObject *object)
1599 {
1600   GDesktopAppInfo *info;
1601
1602   info = G_DESKTOP_APP_INFO (object);
1603
1604   g_free (info->desktop_id);
1605   g_free (info->filename);
1606
1607   if (info->keyfile)
1608     g_key_file_unref (info->keyfile);
1609
1610   g_free (info->name);
1611   g_free (info->generic_name);
1612   g_free (info->fullname);
1613   g_free (info->comment);
1614   g_free (info->icon_name);
1615   if (info->icon)
1616     g_object_unref (info->icon);
1617   g_strfreev (info->keywords);
1618   g_strfreev (info->only_show_in);
1619   g_strfreev (info->not_show_in);
1620   g_free (info->try_exec);
1621   g_free (info->exec);
1622   g_free (info->binary);
1623   g_free (info->path);
1624   g_free (info->categories);
1625   g_free (info->startup_wm_class);
1626   g_strfreev (info->mime_types);
1627   g_free (info->app_id);
1628   g_strfreev (info->actions);
1629
1630   G_OBJECT_CLASS (g_desktop_app_info_parent_class)->finalize (object);
1631 }
1632
1633 static void
1634 g_desktop_app_info_set_property (GObject      *object,
1635                                  guint         prop_id,
1636                                  const GValue *value,
1637                                  GParamSpec   *pspec)
1638 {
1639   GDesktopAppInfo *self = G_DESKTOP_APP_INFO (object);
1640
1641   switch (prop_id)
1642     {
1643     case PROP_FILENAME:
1644       self->filename = g_value_dup_string (value);
1645       break;
1646
1647     default:
1648       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1649       break;
1650     }
1651 }
1652
1653 static void
1654 g_desktop_app_info_get_property (GObject    *object,
1655                                  guint       prop_id,
1656                                  GValue     *value,
1657                                  GParamSpec *pspec)
1658 {
1659   GDesktopAppInfo *self = G_DESKTOP_APP_INFO (object);
1660
1661   switch (prop_id)
1662     {
1663     case PROP_FILENAME:
1664       g_value_set_string (value, self->filename);
1665       break;
1666     default:
1667       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1668       break;
1669     }
1670 }
1671
1672 static void
1673 g_desktop_app_info_class_init (GDesktopAppInfoClass *klass)
1674 {
1675   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
1676
1677   gobject_class->get_property = g_desktop_app_info_get_property;
1678   gobject_class->set_property = g_desktop_app_info_set_property;
1679   gobject_class->finalize = g_desktop_app_info_finalize;
1680
1681   /**
1682    * GDesktopAppInfo:filename:
1683    *
1684    * The origin filename of this #GDesktopAppInfo
1685    */
1686   g_object_class_install_property (gobject_class,
1687                                    PROP_FILENAME,
1688                                    g_param_spec_string ("filename", "Filename", "", NULL,
1689                                                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1690 }
1691
1692 static void
1693 g_desktop_app_info_init (GDesktopAppInfo *local)
1694 {
1695 }
1696
1697 /* Construction... {{{2 */
1698
1699 /*< internal >
1700  * binary_from_exec:
1701  * @exec: an exec line
1702  *
1703  * Returns the first word in an exec line (ie: the binary name).
1704  *
1705  * If @exec is "  progname --foo %F" then returns "progname".
1706  */
1707 static char *
1708 binary_from_exec (const char *exec)
1709 {
1710   const char *p, *start;
1711
1712   p = exec;
1713   while (*p == ' ')
1714     p++;
1715   start = p;
1716   while (*p != ' ' && *p != 0)
1717     p++;
1718
1719   return g_strndup (start, p - start);
1720 }
1721
1722 static gboolean
1723 g_desktop_app_info_load_from_keyfile (GDesktopAppInfo *info,
1724                                       GKeyFile        *key_file)
1725 {
1726   char *start_group;
1727   char *type;
1728   char *try_exec;
1729   char *exec;
1730   gboolean bus_activatable;
1731
1732   start_group = g_key_file_get_start_group (key_file);
1733   if (start_group == NULL || strcmp (start_group, G_KEY_FILE_DESKTOP_GROUP) != 0)
1734     {
1735       g_free (start_group);
1736       return FALSE;
1737     }
1738   g_free (start_group);
1739
1740   type = g_key_file_get_string (key_file,
1741                                 G_KEY_FILE_DESKTOP_GROUP,
1742                                 G_KEY_FILE_DESKTOP_KEY_TYPE,
1743                                 NULL);
1744   if (type == NULL || strcmp (type, G_KEY_FILE_DESKTOP_TYPE_APPLICATION) != 0)
1745     {
1746       g_free (type);
1747       return FALSE;
1748     }
1749   g_free (type);
1750
1751   try_exec = g_key_file_get_string (key_file,
1752                                     G_KEY_FILE_DESKTOP_GROUP,
1753                                     G_KEY_FILE_DESKTOP_KEY_TRY_EXEC,
1754                                     NULL);
1755   if (try_exec && try_exec[0] != '\0')
1756     {
1757       char *t;
1758       t = g_find_program_in_path (try_exec);
1759       if (t == NULL)
1760         {
1761           g_free (try_exec);
1762           return FALSE;
1763         }
1764       g_free (t);
1765     }
1766
1767   exec = g_key_file_get_string (key_file,
1768                                 G_KEY_FILE_DESKTOP_GROUP,
1769                                 G_KEY_FILE_DESKTOP_KEY_EXEC,
1770                                 NULL);
1771   if (exec && exec[0] != '\0')
1772     {
1773       gint argc;
1774       char **argv;
1775       if (!g_shell_parse_argv (exec, &argc, &argv, NULL))
1776         {
1777           g_free (exec);
1778           g_free (try_exec);
1779           return FALSE;
1780         }
1781       else
1782         {
1783           char *t;
1784           t = g_find_program_in_path (argv[0]);
1785           g_strfreev (argv);
1786
1787           if (t == NULL)
1788             {
1789               g_free (exec);
1790               g_free (try_exec);
1791               return FALSE;
1792             }
1793           g_free (t);
1794         }
1795     }
1796
1797   info->name = g_key_file_get_locale_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NAME, NULL, NULL);
1798   info->generic_name = g_key_file_get_locale_string (key_file, G_KEY_FILE_DESKTOP_GROUP, GENERIC_NAME_KEY, NULL, NULL);
1799   info->fullname = g_key_file_get_locale_string (key_file, G_KEY_FILE_DESKTOP_GROUP, FULL_NAME_KEY, NULL, NULL);
1800   info->keywords = g_key_file_get_locale_string_list (key_file, G_KEY_FILE_DESKTOP_GROUP, KEYWORDS_KEY, NULL, NULL, NULL);
1801   info->comment = g_key_file_get_locale_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_COMMENT, NULL, NULL);
1802   info->nodisplay = g_key_file_get_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY, NULL) != FALSE;
1803   info->icon_name =  g_key_file_get_locale_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ICON, NULL, NULL);
1804   info->only_show_in = g_key_file_get_string_list (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ONLY_SHOW_IN, NULL, NULL);
1805   info->not_show_in = g_key_file_get_string_list (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NOT_SHOW_IN, NULL, NULL);
1806   info->try_exec = try_exec;
1807   info->exec = exec;
1808   info->path = g_key_file_get_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_PATH, NULL);
1809   info->terminal = g_key_file_get_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_TERMINAL, NULL) != FALSE;
1810   info->startup_notify = g_key_file_get_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_STARTUP_NOTIFY, NULL) != FALSE;
1811   info->no_fuse = g_key_file_get_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP, "X-GIO-NoFuse", NULL) != FALSE;
1812   info->hidden = g_key_file_get_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_HIDDEN, NULL) != FALSE;
1813   info->categories = g_key_file_get_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_CATEGORIES, NULL);
1814   info->startup_wm_class = g_key_file_get_string (key_file, G_KEY_FILE_DESKTOP_GROUP, STARTUP_WM_CLASS_KEY, NULL);
1815   info->mime_types = g_key_file_get_string_list (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_MIME_TYPE, NULL, NULL);
1816   bus_activatable = g_key_file_get_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_DBUS_ACTIVATABLE, NULL);
1817   info->actions = g_key_file_get_string_list (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ACTIONS, NULL, NULL);
1818
1819   /* Remove the special-case: no Actions= key just means 0 extra actions */
1820   if (info->actions == NULL)
1821     info->actions = g_new0 (gchar *, 0 + 1);
1822
1823   info->icon = NULL;
1824   if (info->icon_name)
1825     {
1826       if (g_path_is_absolute (info->icon_name))
1827         {
1828           GFile *file;
1829
1830           file = g_file_new_for_path (info->icon_name);
1831           info->icon = g_file_icon_new (file);
1832           g_object_unref (file);
1833         }
1834       else
1835         {
1836           char *p;
1837
1838           /* Work around a common mistake in desktop files */
1839           if ((p = strrchr (info->icon_name, '.')) != NULL &&
1840               (strcmp (p, ".png") == 0 ||
1841                strcmp (p, ".xpm") == 0 ||
1842                strcmp (p, ".svg") == 0))
1843             *p = 0;
1844
1845           info->icon = g_themed_icon_new (info->icon_name);
1846         }
1847     }
1848
1849   if (info->exec)
1850     info->binary = binary_from_exec (info->exec);
1851
1852   if (info->path && info->path[0] == '\0')
1853     {
1854       g_free (info->path);
1855       info->path = NULL;
1856     }
1857
1858   /* Can only be DBusActivatable if we know the filename, which means
1859    * that this won't work for the load-from-keyfile case.
1860    */
1861   if (bus_activatable && info->filename)
1862     {
1863       gchar *basename;
1864       gchar *last_dot;
1865
1866       basename = g_path_get_basename (info->filename);
1867       last_dot = strrchr (basename, '.');
1868
1869       if (last_dot && g_str_equal (last_dot, ".desktop"))
1870         {
1871           *last_dot = '\0';
1872
1873           if (g_dbus_is_name (basename) && basename[0] != ':')
1874             info->app_id = g_strdup (basename);
1875         }
1876
1877       g_free (basename);
1878     }
1879
1880   info->keyfile = g_key_file_ref (key_file);
1881
1882   return TRUE;
1883 }
1884
1885 static gboolean
1886 g_desktop_app_info_load_file (GDesktopAppInfo *self)
1887 {
1888   GKeyFile *key_file;
1889   gboolean retval = FALSE;
1890
1891   g_return_val_if_fail (self->filename != NULL, FALSE);
1892
1893   self->desktop_id = g_path_get_basename (self->filename);
1894
1895   key_file = g_key_file_new ();
1896
1897   if (g_key_file_load_from_file (key_file, self->filename, G_KEY_FILE_NONE, NULL))
1898     retval = g_desktop_app_info_load_from_keyfile (self, key_file);
1899
1900   g_key_file_unref (key_file);
1901   return retval;
1902 }
1903
1904 /**
1905  * g_desktop_app_info_new_from_keyfile:
1906  * @key_file: an opened #GKeyFile
1907  *
1908  * Creates a new #GDesktopAppInfo.
1909  *
1910  * Returns: (nullable): a new #GDesktopAppInfo or %NULL on error.
1911  *
1912  * Since: 2.18
1913  **/
1914 GDesktopAppInfo *
1915 g_desktop_app_info_new_from_keyfile (GKeyFile *key_file)
1916 {
1917   GDesktopAppInfo *info;
1918
1919   info = g_object_new (G_TYPE_DESKTOP_APP_INFO, NULL);
1920   info->filename = NULL;
1921   if (!g_desktop_app_info_load_from_keyfile (info, key_file))
1922     {
1923       g_object_unref (info);
1924       return NULL;
1925     }
1926   return info;
1927 }
1928
1929 /**
1930  * g_desktop_app_info_new_from_filename:
1931  * @filename: (type filename): the path of a desktop file, in the GLib
1932  *      filename encoding
1933  *
1934  * Creates a new #GDesktopAppInfo.
1935  *
1936  * Returns: (nullable): a new #GDesktopAppInfo or %NULL on error.
1937  **/
1938 GDesktopAppInfo *
1939 g_desktop_app_info_new_from_filename (const char *filename)
1940 {
1941   GDesktopAppInfo *info = NULL;
1942
1943   info = g_object_new (G_TYPE_DESKTOP_APP_INFO, "filename", filename, NULL);
1944   if (!g_desktop_app_info_load_file (info))
1945     {
1946       g_object_unref (info);
1947       return NULL;
1948     }
1949   return info;
1950 }
1951
1952 /**
1953  * g_desktop_app_info_new:
1954  * @desktop_id: the desktop file id
1955  *
1956  * Creates a new #GDesktopAppInfo based on a desktop file id.
1957  *
1958  * A desktop file id is the basename of the desktop file, including the
1959  * .desktop extension. GIO is looking for a desktop file with this name
1960  * in the `applications` subdirectories of the XDG
1961  * data directories (i.e. the directories specified in the `XDG_DATA_HOME`
1962  * and `XDG_DATA_DIRS` environment variables). GIO also supports the
1963  * prefix-to-subdirectory mapping that is described in the
1964  * [Menu Spec](http://standards.freedesktop.org/menu-spec/latest/)
1965  * (i.e. a desktop id of kde-foo.desktop will match
1966  * `/usr/share/applications/kde/foo.desktop`).
1967  *
1968  * Returns: (nullable): a new #GDesktopAppInfo, or %NULL if no desktop
1969  *     file with that id exists.
1970  */
1971 GDesktopAppInfo *
1972 g_desktop_app_info_new (const char *desktop_id)
1973 {
1974   GDesktopAppInfo *appinfo = NULL;
1975   guint i;
1976
1977   desktop_file_dirs_lock ();
1978
1979   for (i = 0; i < desktop_file_dirs->len; i++)
1980     {
1981       appinfo = desktop_file_dir_get_app (g_ptr_array_index (desktop_file_dirs, i), desktop_id);
1982
1983       if (appinfo)
1984         break;
1985     }
1986
1987   desktop_file_dirs_unlock ();
1988
1989   if (appinfo == NULL)
1990     return NULL;
1991
1992   g_free (appinfo->desktop_id);
1993   appinfo->desktop_id = g_strdup (desktop_id);
1994
1995   if (g_desktop_app_info_get_is_hidden (appinfo))
1996     {
1997       g_object_unref (appinfo);
1998       appinfo = NULL;
1999     }
2000
2001   return appinfo;
2002 }
2003
2004 static GAppInfo *
2005 g_desktop_app_info_dup (GAppInfo *appinfo)
2006 {
2007   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
2008   GDesktopAppInfo *new_info;
2009
2010   new_info = g_object_new (G_TYPE_DESKTOP_APP_INFO, NULL);
2011
2012   new_info->filename = g_strdup (info->filename);
2013   new_info->desktop_id = g_strdup (info->desktop_id);
2014
2015   if (info->keyfile)
2016     new_info->keyfile = g_key_file_ref (info->keyfile);
2017
2018   new_info->name = g_strdup (info->name);
2019   new_info->generic_name = g_strdup (info->generic_name);
2020   new_info->fullname = g_strdup (info->fullname);
2021   new_info->keywords = g_strdupv (info->keywords);
2022   new_info->comment = g_strdup (info->comment);
2023   new_info->nodisplay = info->nodisplay;
2024   new_info->icon_name = g_strdup (info->icon_name);
2025   if (info->icon)
2026     new_info->icon = g_object_ref (info->icon);
2027   new_info->only_show_in = g_strdupv (info->only_show_in);
2028   new_info->not_show_in = g_strdupv (info->not_show_in);
2029   new_info->try_exec = g_strdup (info->try_exec);
2030   new_info->exec = g_strdup (info->exec);
2031   new_info->binary = g_strdup (info->binary);
2032   new_info->path = g_strdup (info->path);
2033   new_info->app_id = g_strdup (info->app_id);
2034   new_info->hidden = info->hidden;
2035   new_info->terminal = info->terminal;
2036   new_info->startup_notify = info->startup_notify;
2037
2038   return G_APP_INFO (new_info);
2039 }
2040
2041 /* GAppInfo interface implementation functions {{{2 */
2042
2043 static gboolean
2044 g_desktop_app_info_equal (GAppInfo *appinfo1,
2045                           GAppInfo *appinfo2)
2046 {
2047   GDesktopAppInfo *info1 = G_DESKTOP_APP_INFO (appinfo1);
2048   GDesktopAppInfo *info2 = G_DESKTOP_APP_INFO (appinfo2);
2049
2050   if (info1->desktop_id == NULL ||
2051       info2->desktop_id == NULL)
2052     return info1 == info2;
2053
2054   return strcmp (info1->desktop_id, info2->desktop_id) == 0;
2055 }
2056
2057 static const char *
2058 g_desktop_app_info_get_id (GAppInfo *appinfo)
2059 {
2060   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
2061
2062   return info->desktop_id;
2063 }
2064
2065 static const char *
2066 g_desktop_app_info_get_name (GAppInfo *appinfo)
2067 {
2068   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
2069
2070   if (info->name == NULL)
2071     return _("Unnamed");
2072   return info->name;
2073 }
2074
2075 static const char *
2076 g_desktop_app_info_get_display_name (GAppInfo *appinfo)
2077 {
2078   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
2079
2080   if (info->fullname == NULL)
2081     return g_desktop_app_info_get_name (appinfo);
2082   return info->fullname;
2083 }
2084
2085 /**
2086  * g_desktop_app_info_get_is_hidden:
2087  * @info: a #GDesktopAppInfo.
2088  *
2089  * A desktop file is hidden if the Hidden key in it is
2090  * set to True.
2091  *
2092  * Returns: %TRUE if hidden, %FALSE otherwise.
2093  **/
2094 gboolean
2095 g_desktop_app_info_get_is_hidden (GDesktopAppInfo *info)
2096 {
2097   return info->hidden;
2098 }
2099
2100 /**
2101  * g_desktop_app_info_get_filename:
2102  * @info: a #GDesktopAppInfo
2103  *
2104  * When @info was created from a known filename, return it.  In some
2105  * situations such as the #GDesktopAppInfo returned from
2106  * g_desktop_app_info_new_from_keyfile(), this function will return %NULL.
2107  *
2108  * Returns: (type filename): The full path to the file for @info,
2109  *     or %NULL if not known.
2110  * Since: 2.24
2111  */
2112 const char *
2113 g_desktop_app_info_get_filename (GDesktopAppInfo *info)
2114 {
2115   return info->filename;
2116 }
2117
2118 static const char *
2119 g_desktop_app_info_get_description (GAppInfo *appinfo)
2120 {
2121   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
2122
2123   return info->comment;
2124 }
2125
2126 static const char *
2127 g_desktop_app_info_get_executable (GAppInfo *appinfo)
2128 {
2129   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
2130
2131   return info->binary;
2132 }
2133
2134 static const char *
2135 g_desktop_app_info_get_commandline (GAppInfo *appinfo)
2136 {
2137   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
2138
2139   return info->exec;
2140 }
2141
2142 static GIcon *
2143 g_desktop_app_info_get_icon (GAppInfo *appinfo)
2144 {
2145   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
2146
2147   return info->icon;
2148 }
2149
2150 /**
2151  * g_desktop_app_info_get_categories:
2152  * @info: a #GDesktopAppInfo
2153  *
2154  * Gets the categories from the desktop file.
2155  *
2156  * Returns: The unparsed Categories key from the desktop file;
2157  *     i.e. no attempt is made to split it by ';' or validate it.
2158  */
2159 const char *
2160 g_desktop_app_info_get_categories (GDesktopAppInfo *info)
2161 {
2162   return info->categories;
2163 }
2164
2165 /**
2166  * g_desktop_app_info_get_keywords:
2167  * @info: a #GDesktopAppInfo
2168  *
2169  * Gets the keywords from the desktop file.
2170  *
2171  * Returns: (transfer none): The value of the Keywords key
2172  *
2173  * Since: 2.32
2174  */
2175 const char * const *
2176 g_desktop_app_info_get_keywords (GDesktopAppInfo *info)
2177 {
2178   return (const char * const *)info->keywords;
2179 }
2180
2181 /**
2182  * g_desktop_app_info_get_generic_name:
2183  * @info: a #GDesktopAppInfo
2184  *
2185  * Gets the generic name from the destkop file.
2186  *
2187  * Returns: The value of the GenericName key
2188  */
2189 const char *
2190 g_desktop_app_info_get_generic_name (GDesktopAppInfo *info)
2191 {
2192   return info->generic_name;
2193 }
2194
2195 /**
2196  * g_desktop_app_info_get_nodisplay:
2197  * @info: a #GDesktopAppInfo
2198  *
2199  * Gets the value of the NoDisplay key, which helps determine if the
2200  * application info should be shown in menus. See
2201  * #G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY and g_app_info_should_show().
2202  *
2203  * Returns: The value of the NoDisplay key
2204  *
2205  * Since: 2.30
2206  */
2207 gboolean
2208 g_desktop_app_info_get_nodisplay (GDesktopAppInfo *info)
2209 {
2210   return info->nodisplay;
2211 }
2212
2213 /**
2214  * g_desktop_app_info_get_show_in:
2215  * @info: a #GDesktopAppInfo
2216  * @desktop_env: (nullable): a string specifying a desktop name
2217  *
2218  * Checks if the application info should be shown in menus that list available
2219  * applications for a specific name of the desktop, based on the
2220  * `OnlyShowIn` and `NotShowIn` keys.
2221  *
2222  * @desktop_env should typically be given as %NULL, in which case the
2223  * `XDG_CURRENT_DESKTOP` environment variable is consulted.  If you want
2224  * to override the default mechanism then you may specify @desktop_env,
2225  * but this is not recommended.
2226  *
2227  * Note that g_app_info_should_show() for @info will include this check (with
2228  * %NULL for @desktop_env) as well as additional checks.
2229  *
2230  * Returns: %TRUE if the @info should be shown in @desktop_env according to the
2231  * `OnlyShowIn` and `NotShowIn` keys, %FALSE
2232  * otherwise.
2233  *
2234  * Since: 2.30
2235  */
2236 gboolean
2237 g_desktop_app_info_get_show_in (GDesktopAppInfo *info,
2238                                 const gchar     *desktop_env)
2239 {
2240   const gchar *specified_envs[] = { desktop_env, NULL };
2241   const gchar * const *envs;
2242   gint i;
2243
2244   g_return_val_if_fail (G_IS_DESKTOP_APP_INFO (info), FALSE);
2245
2246   if (desktop_env)
2247     envs = specified_envs;
2248   else
2249     envs = get_current_desktops (NULL);
2250
2251   for (i = 0; envs[i]; i++)
2252     {
2253       gint j;
2254
2255       if (info->only_show_in)
2256         for (j = 0; info->only_show_in[j]; j++)
2257           if (g_str_equal (info->only_show_in[j], envs[i]))
2258             return TRUE;
2259
2260       if (info->not_show_in)
2261         for (j = 0; info->not_show_in[j]; j++)
2262           if (g_str_equal (info->not_show_in[j], envs[i]))
2263             return FALSE;
2264     }
2265
2266   return info->only_show_in == NULL;
2267 }
2268
2269 /* Launching... {{{2 */
2270
2271 static char *
2272 expand_macro_single (char macro, const char *uri)
2273 {
2274   GFile *file;
2275   char *result = NULL;
2276   char *path = NULL;
2277   char *name;
2278
2279   file = g_file_new_for_uri (uri);
2280
2281   switch (macro)
2282     {
2283     case 'u':
2284     case 'U':
2285       result = g_shell_quote (uri);
2286       break;
2287     case 'f':
2288     case 'F':
2289       path = g_file_get_path (file);
2290       if (path)
2291         result = g_shell_quote (path);
2292       break;
2293     case 'd':
2294     case 'D':
2295       path = g_file_get_path (file);
2296       if (path)
2297         {
2298           name = g_path_get_dirname (path);
2299           result = g_shell_quote (name);
2300           g_free (name);
2301         }
2302       break;
2303     case 'n':
2304     case 'N':
2305       path = g_file_get_path (file);
2306       if (path)
2307         {
2308           name = g_path_get_basename (path);
2309           result = g_shell_quote (name);
2310           g_free (name);
2311         }
2312       break;
2313     }
2314
2315   g_object_unref (file);
2316   g_free (path);
2317
2318   return result;
2319 }
2320
2321 static char *
2322 expand_macro_uri (char macro, const char *uri, gboolean force_file_uri, char force_file_uri_macro)
2323 {
2324   char *expanded = NULL;
2325
2326   g_return_val_if_fail (uri != NULL, NULL);
2327
2328   if (!force_file_uri ||
2329       /* Pass URI if it contains an anchor */
2330       strchr (uri, '#') != NULL)
2331     {
2332       expanded = expand_macro_single (macro, uri);
2333     }
2334   else
2335     {
2336       expanded = expand_macro_single (force_file_uri_macro, uri);
2337       if (expanded == NULL)
2338         expanded = expand_macro_single (macro, uri);
2339     }
2340
2341   return expanded;
2342 }
2343
2344 static void
2345 expand_macro (char              macro,
2346               GString          *exec,
2347               GDesktopAppInfo  *info,
2348               GList           **uri_list)
2349 {
2350   GList *uris = *uri_list;
2351   char *expanded = NULL;
2352   gboolean force_file_uri;
2353   char force_file_uri_macro;
2354   const char *uri;
2355
2356   g_return_if_fail (exec != NULL);
2357
2358   /* On %u and %U, pass POSIX file path pointing to the URI via
2359    * the FUSE mount in ~/.gvfs. Note that if the FUSE daemon isn't
2360    * running or the URI doesn't have a POSIX file path via FUSE
2361    * we'll just pass the URI.
2362    */
2363   force_file_uri_macro = macro;
2364   force_file_uri = FALSE;
2365   if (!info->no_fuse)
2366     {
2367       switch (macro)
2368         {
2369         case 'u':
2370           force_file_uri_macro = 'f';
2371           force_file_uri = TRUE;
2372           break;
2373         case 'U':
2374           force_file_uri_macro = 'F';
2375           force_file_uri = TRUE;
2376           break;
2377         default:
2378           break;
2379         }
2380     }
2381
2382   switch (macro)
2383     {
2384     case 'u':
2385     case 'f':
2386     case 'd':
2387     case 'n':
2388       if (uris)
2389         {
2390           uri = uris->data;
2391           expanded = expand_macro_uri (macro, uri,
2392                                        force_file_uri, force_file_uri_macro);
2393           if (expanded)
2394             {
2395               g_string_append (exec, expanded);
2396               g_free (expanded);
2397             }
2398           uris = uris->next;
2399         }
2400
2401       break;
2402
2403     case 'U':
2404     case 'F':
2405     case 'D':
2406     case 'N':
2407       while (uris)
2408         {
2409           uri = uris->data;
2410           expanded = expand_macro_uri (macro, uri,
2411                                        force_file_uri, force_file_uri_macro);
2412           if (expanded)
2413             {
2414               g_string_append (exec, expanded);
2415               g_free (expanded);
2416             }
2417
2418           uris = uris->next;
2419
2420           if (uris != NULL && expanded)
2421             g_string_append_c (exec, ' ');
2422         }
2423
2424       break;
2425
2426     case 'i':
2427       if (info->icon_name)
2428         {
2429           g_string_append (exec, "--icon ");
2430           expanded = g_shell_quote (info->icon_name);
2431           g_string_append (exec, expanded);
2432           g_free (expanded);
2433         }
2434       break;
2435
2436     case 'c':
2437       if (info->name)
2438         {
2439           expanded = g_shell_quote (info->name);
2440           g_string_append (exec, expanded);
2441           g_free (expanded);
2442         }
2443       break;
2444
2445     case 'k':
2446       if (info->filename)
2447         {
2448           expanded = g_shell_quote (info->filename);
2449           g_string_append (exec, expanded);
2450           g_free (expanded);
2451         }
2452       break;
2453
2454     case 'm': /* deprecated */
2455       break;
2456
2457     case '%':
2458       g_string_append_c (exec, '%');
2459       break;
2460     }
2461
2462   *uri_list = uris;
2463 }
2464
2465 static gboolean
2466 expand_application_parameters (GDesktopAppInfo   *info,
2467                                const gchar       *exec_line,
2468                                GList            **uris,
2469                                int               *argc,
2470                                char            ***argv,
2471                                GError           **error)
2472 {
2473   GList *uri_list = *uris;
2474   const char *p = exec_line;
2475   GString *expanded_exec;
2476   gboolean res;
2477
2478   if (exec_line == NULL)
2479     {
2480       g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
2481                            _("Desktop file didn’t specify Exec field"));
2482       return FALSE;
2483     }
2484
2485   expanded_exec = g_string_new (NULL);
2486
2487   while (*p)
2488     {
2489       if (p[0] == '%' && p[1] != '\0')
2490         {
2491           expand_macro (p[1], expanded_exec, info, uris);
2492           p++;
2493         }
2494       else
2495         g_string_append_c (expanded_exec, *p);
2496
2497       p++;
2498     }
2499
2500   /* No file substitutions */
2501   if (uri_list == *uris && uri_list != NULL)
2502     {
2503       /* If there is no macro default to %f. This is also what KDE does */
2504       g_string_append_c (expanded_exec, ' ');
2505       expand_macro ('f', expanded_exec, info, uris);
2506     }
2507
2508   res = g_shell_parse_argv (expanded_exec->str, argc, argv, error);
2509   g_string_free (expanded_exec, TRUE);
2510   return res;
2511 }
2512
2513 static gboolean
2514 prepend_terminal_to_vector (int    *argc,
2515                             char ***argv)
2516 {
2517 #ifndef G_OS_WIN32
2518   char **real_argv;
2519   int real_argc;
2520   int i, j;
2521   char **term_argv = NULL;
2522   int term_argc = 0;
2523   char *check;
2524   char **the_argv;
2525
2526   g_return_val_if_fail (argc != NULL, FALSE);
2527   g_return_val_if_fail (argv != NULL, FALSE);
2528
2529   /* sanity */
2530   if(*argv == NULL)
2531     *argc = 0;
2532
2533   the_argv = *argv;
2534
2535   /* compute size if not given */
2536   if (*argc < 0)
2537     {
2538       for (i = 0; the_argv[i] != NULL; i++)
2539         ;
2540       *argc = i;
2541     }
2542
2543   term_argc = 2;
2544   term_argv = g_new0 (char *, 3);
2545
2546   check = g_find_program_in_path ("gnome-terminal");
2547   if (check != NULL)
2548     {
2549       term_argv[0] = check;
2550       /* Since 2017, gnome-terminal has preferred `--` over `-x` or `-e`. */
2551       term_argv[1] = g_strdup ("--");
2552     }
2553   else
2554     {
2555       if (check == NULL)
2556         check = g_find_program_in_path ("mate-terminal");
2557       if (check == NULL)
2558         check = g_find_program_in_path ("xfce4-terminal");
2559       if (check != NULL)
2560         {
2561           term_argv[0] = check;
2562           /* Note that gnome-terminal takes -x and
2563            * as -e in gnome-terminal is broken we use that. */
2564           term_argv[1] = g_strdup ("-x");
2565         }
2566       else
2567         {
2568           if (check == NULL)
2569             check = g_find_program_in_path ("nxterm");
2570           if (check == NULL)
2571             check = g_find_program_in_path ("color-xterm");
2572           if (check == NULL)
2573             check = g_find_program_in_path ("rxvt");
2574           if (check == NULL)
2575             check = g_find_program_in_path ("dtterm");
2576           if (check == NULL)
2577             {
2578               check = g_strdup ("xterm");
2579               g_debug ("Couldn’t find a terminal: falling back to xterm");
2580             }
2581           term_argv[0] = check;
2582           term_argv[1] = g_strdup ("-e");
2583         }
2584     }
2585
2586   real_argc = term_argc + *argc;
2587   real_argv = g_new (char *, real_argc + 1);
2588
2589   for (i = 0; i < term_argc; i++)
2590     real_argv[i] = term_argv[i];
2591
2592   for (j = 0; j < *argc; j++, i++)
2593     real_argv[i] = (char *)the_argv[j];
2594
2595   real_argv[i] = NULL;
2596
2597   g_free (*argv);
2598   *argv = real_argv;
2599   *argc = real_argc;
2600
2601   /* we use g_free here as we sucked all the inner strings
2602    * out from it into real_argv */
2603   g_free (term_argv);
2604   return TRUE;
2605 #else
2606   return FALSE;
2607 #endif /* G_OS_WIN32 */
2608 }
2609
2610 static GList *
2611 create_files_for_uris (GList *uris)
2612 {
2613   GList *res;
2614   GList *iter;
2615
2616   res = NULL;
2617
2618   for (iter = uris; iter; iter = iter->next)
2619     {
2620       GFile *file = g_file_new_for_uri ((char *)iter->data);
2621       res = g_list_prepend (res, file);
2622     }
2623
2624   return g_list_reverse (res);
2625 }
2626
2627 static void
2628 notify_desktop_launch (GDBusConnection  *session_bus,
2629                        GDesktopAppInfo  *info,
2630                        long              pid,
2631                        const char       *display,
2632                        const char       *sn_id,
2633                        GList            *uris)
2634 {
2635   GDBusMessage *msg;
2636   GVariantBuilder uri_variant;
2637   GVariantBuilder extras_variant;
2638   GList *iter;
2639   const char *desktop_file_id;
2640   const char *gio_desktop_file;
2641
2642   if (session_bus == NULL)
2643     return;
2644
2645   g_variant_builder_init (&uri_variant, G_VARIANT_TYPE ("as"));
2646   for (iter = uris; iter; iter = iter->next)
2647     g_variant_builder_add (&uri_variant, "s", iter->data);
2648
2649   g_variant_builder_init (&extras_variant, G_VARIANT_TYPE ("a{sv}"));
2650   if (sn_id != NULL && g_utf8_validate (sn_id, -1, NULL))
2651     g_variant_builder_add (&extras_variant, "{sv}",
2652                            "startup-id",
2653                            g_variant_new ("s",
2654                                           sn_id));
2655   gio_desktop_file = g_getenv ("GIO_LAUNCHED_DESKTOP_FILE");
2656   if (gio_desktop_file != NULL)
2657     g_variant_builder_add (&extras_variant, "{sv}",
2658                            "origin-desktop-file",
2659                            g_variant_new_bytestring (gio_desktop_file));
2660   if (g_get_prgname () != NULL)
2661     g_variant_builder_add (&extras_variant, "{sv}",
2662                            "origin-prgname",
2663                            g_variant_new_bytestring (g_get_prgname ()));
2664   g_variant_builder_add (&extras_variant, "{sv}",
2665                          "origin-pid",
2666                          g_variant_new ("x",
2667                                         (gint64)getpid ()));
2668
2669   if (info->filename)
2670     desktop_file_id = info->filename;
2671   else if (info->desktop_id)
2672     desktop_file_id = info->desktop_id;
2673   else
2674     desktop_file_id = "";
2675
2676   msg = g_dbus_message_new_signal ("/org/gtk/gio/DesktopAppInfo",
2677                                    "org.gtk.gio.DesktopAppInfo",
2678                                    "Launched");
2679   g_dbus_message_set_body (msg, g_variant_new ("(@aysxasa{sv})",
2680                                                g_variant_new_bytestring (desktop_file_id),
2681                                                display ? display : "",
2682                                                (gint64)pid,
2683                                                &uri_variant,
2684                                                &extras_variant));
2685   g_dbus_connection_send_message (session_bus,
2686                                   msg, 0,
2687                                   NULL,
2688                                   NULL);
2689   g_object_unref (msg);
2690 }
2691
2692 #define _SPAWN_FLAGS_DEFAULT (G_SPAWN_SEARCH_PATH)
2693
2694 static gboolean
2695 g_desktop_app_info_launch_uris_with_spawn (GDesktopAppInfo            *info,
2696                                            GDBusConnection            *session_bus,
2697                                            const gchar                *exec_line,
2698                                            GList                      *uris,
2699                                            GAppLaunchContext          *launch_context,
2700                                            GSpawnFlags                 spawn_flags,
2701                                            GSpawnChildSetupFunc        user_setup,
2702                                            gpointer                    user_setup_data,
2703                                            GDesktopAppLaunchCallback   pid_callback,
2704                                            gpointer                    pid_callback_data,
2705                                            gint                        stdin_fd,
2706                                            gint                        stdout_fd,
2707                                            gint                        stderr_fd,
2708                                            GError                    **error)
2709 {
2710   gboolean completed = FALSE;
2711   GList *old_uris;
2712   GList *dup_uris;
2713
2714   char **argv, **envp;
2715   int argc;
2716
2717   g_return_val_if_fail (info != NULL, FALSE);
2718
2719   argv = NULL;
2720
2721   if (launch_context)
2722     envp = g_app_launch_context_get_environment (launch_context);
2723   else
2724     envp = g_get_environ ();
2725
2726   /* The GList* passed to expand_application_parameters() will be modified
2727    * internally by expand_macro(), so we need to pass a copy of it instead,
2728    * and also use that copy to control the exit condition of the loop below.
2729    */
2730   dup_uris = uris;
2731   do
2732     {
2733       GPid pid;
2734       GList *launched_uris;
2735       GList *iter;
2736       char *sn_id = NULL;
2737       char **wrapped_argv;
2738       int i;
2739       const gchar * const wrapper_argv[] =
2740         {
2741           "/bin/sh",
2742           "-e",
2743           "-u",
2744           "-c", "export GIO_LAUNCHED_DESKTOP_FILE_PID=$$; exec \"$@\"",
2745           "sh",  /* argv[0] for sh */
2746         };
2747
2748       old_uris = dup_uris;
2749       if (!expand_application_parameters (info, exec_line, &dup_uris, &argc, &argv, error))
2750         goto out;
2751
2752       /* Get the subset of URIs we're launching with this process */
2753       launched_uris = NULL;
2754       for (iter = old_uris; iter != NULL && iter != dup_uris; iter = iter->next)
2755         launched_uris = g_list_prepend (launched_uris, iter->data);
2756       launched_uris = g_list_reverse (launched_uris);
2757
2758       if (info->terminal && !prepend_terminal_to_vector (&argc, &argv))
2759         {
2760           g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
2761                                _("Unable to find terminal required for application"));
2762           goto out;
2763         }
2764
2765       if (info->filename)
2766         envp = g_environ_setenv (envp,
2767                                  "GIO_LAUNCHED_DESKTOP_FILE",
2768                                  info->filename,
2769                                  TRUE);
2770
2771       sn_id = NULL;
2772       if (launch_context)
2773         {
2774           GList *launched_files = create_files_for_uris (launched_uris);
2775
2776           if (info->startup_notify)
2777             {
2778               sn_id = g_app_launch_context_get_startup_notify_id (launch_context,
2779                                                                   G_APP_INFO (info),
2780                                                                   launched_files);
2781               if (sn_id)
2782                 envp = g_environ_setenv (envp, "DESKTOP_STARTUP_ID", sn_id, TRUE);
2783             }
2784
2785           g_list_free_full (launched_files, g_object_unref);
2786         }
2787
2788       /* Wrap the @argv in a command which will set the
2789        * `GIO_LAUNCHED_DESKTOP_FILE_PID` environment variable. We can’t set this
2790        * in @envp along with `GIO_LAUNCHED_DESKTOP_FILE` because we need to know
2791        * the PID of the new forked process. We can’t use setenv() between fork()
2792        * and exec() because we’d rather use posix_spawn() for speed.
2793        *
2794        * `sh` should be available on all the platforms that `GDesktopAppInfo`
2795        * currently supports (since they are all POSIX). If additional platforms
2796        * need to be supported in future, it will probably have to be replaced
2797        * with a wrapper program (grep the GLib git history for
2798        * `gio-launch-desktop` for an example of this which could be
2799        * resurrected). */
2800       wrapped_argv = g_new (char *, argc + G_N_ELEMENTS (wrapper_argv) + 1);
2801
2802       for (i = 0; i < G_N_ELEMENTS (wrapper_argv); i++)
2803         wrapped_argv[i] = g_strdup (wrapper_argv[i]);
2804       for (i = 0; i < argc; i++)
2805         wrapped_argv[i + G_N_ELEMENTS (wrapper_argv)] = g_steal_pointer (&argv[i]);
2806
2807       wrapped_argv[i + G_N_ELEMENTS (wrapper_argv)] = NULL;
2808       g_free (argv);
2809       argv = NULL;
2810
2811       if (!g_spawn_async_with_fds (info->path,
2812                                    wrapped_argv,
2813                                    envp,
2814                                    spawn_flags,
2815                                    user_setup,
2816                                    user_setup_data,
2817                                    &pid,
2818                                    stdin_fd,
2819                                    stdout_fd,
2820                                    stderr_fd,
2821                                    error))
2822         {
2823           if (sn_id)
2824             g_app_launch_context_launch_failed (launch_context, sn_id);
2825
2826           g_free (sn_id);
2827           g_list_free (launched_uris);
2828
2829           goto out;
2830         }
2831
2832       if (pid_callback != NULL)
2833         pid_callback (info, pid, pid_callback_data);
2834
2835       if (launch_context != NULL)
2836         {
2837           GVariantBuilder builder;
2838           GVariant *platform_data;
2839
2840           g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
2841           g_variant_builder_add (&builder, "{sv}", "pid", g_variant_new_int32 (pid));
2842           if (sn_id)
2843             g_variant_builder_add (&builder, "{sv}", "startup-notification-id", g_variant_new_string (sn_id));
2844           platform_data = g_variant_ref_sink (g_variant_builder_end (&builder));
2845           g_signal_emit_by_name (launch_context, "launched", info, platform_data);
2846           g_variant_unref (platform_data);
2847         }
2848
2849       notify_desktop_launch (session_bus,
2850                              info,
2851                              pid,
2852                              NULL,
2853                              sn_id,
2854                              launched_uris);
2855
2856       g_free (sn_id);
2857       g_list_free (launched_uris);
2858
2859       g_strfreev (wrapped_argv);
2860       wrapped_argv = NULL;
2861     }
2862   while (dup_uris != NULL);
2863
2864   completed = TRUE;
2865
2866  out:
2867   g_strfreev (argv);
2868   g_strfreev (envp);
2869
2870   return completed;
2871 }
2872
2873 static gchar *
2874 object_path_from_appid (const gchar *appid)
2875 {
2876   gchar *appid_path, *iter;
2877
2878   appid_path = g_strconcat ("/", appid, NULL);
2879   for (iter = appid_path; *iter; iter++)
2880     {
2881       if (*iter == '.')
2882         *iter = '/';
2883
2884       if (*iter == '-')
2885         *iter = '_';
2886     }
2887
2888   return appid_path;
2889 }
2890
2891 static GVariant *
2892 g_desktop_app_info_make_platform_data (GDesktopAppInfo   *info,
2893                                        GList             *uris,
2894                                        GAppLaunchContext *launch_context)
2895 {
2896   GVariantBuilder builder;
2897
2898   g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
2899
2900   if (launch_context)
2901     {
2902       GList *launched_files = create_files_for_uris (uris);
2903
2904       if (info->startup_notify)
2905         {
2906           gchar *sn_id;
2907
2908           sn_id = g_app_launch_context_get_startup_notify_id (launch_context, G_APP_INFO (info), launched_files);
2909           if (sn_id)
2910             g_variant_builder_add (&builder, "{sv}", "desktop-startup-id", g_variant_new_take_string (sn_id));
2911         }
2912
2913       g_list_free_full (launched_files, g_object_unref);
2914     }
2915
2916   return g_variant_builder_end (&builder);
2917 }
2918
2919 static void
2920 launch_uris_with_dbus (GDesktopAppInfo    *info,
2921                        GDBusConnection    *session_bus,
2922                        GList              *uris,
2923                        GAppLaunchContext  *launch_context,
2924                        GCancellable       *cancellable,
2925                        GAsyncReadyCallback callback,
2926                        gpointer            user_data)
2927 {
2928   GVariantBuilder builder;
2929   gchar *object_path;
2930
2931   g_variant_builder_init (&builder, G_VARIANT_TYPE_TUPLE);
2932
2933   if (uris)
2934     {
2935       GList *iter;
2936
2937       g_variant_builder_open (&builder, G_VARIANT_TYPE_STRING_ARRAY);
2938       for (iter = uris; iter; iter = iter->next)
2939         g_variant_builder_add (&builder, "s", iter->data);
2940       g_variant_builder_close (&builder);
2941     }
2942
2943   g_variant_builder_add_value (&builder, g_desktop_app_info_make_platform_data (info, uris, launch_context));
2944
2945   object_path = object_path_from_appid (info->app_id);
2946   g_dbus_connection_call (session_bus, info->app_id, object_path, "org.freedesktop.Application",
2947                           uris ? "Open" : "Activate", g_variant_builder_end (&builder),
2948                           NULL, G_DBUS_CALL_FLAGS_NONE, -1,
2949                           cancellable, callback, user_data);
2950   g_free (object_path);
2951 }
2952
2953 static gboolean
2954 g_desktop_app_info_launch_uris_with_dbus (GDesktopAppInfo    *info,
2955                                           GDBusConnection    *session_bus,
2956                                           GList              *uris,
2957                                           GAppLaunchContext  *launch_context,
2958                                           GCancellable       *cancellable,
2959                                           GAsyncReadyCallback callback,
2960                                           gpointer            user_data)
2961 {
2962   GList *ruris = uris;
2963   char *app_id = NULL;
2964
2965   g_return_val_if_fail (info != NULL, FALSE);
2966
2967 #ifdef G_OS_UNIX
2968   app_id = g_desktop_app_info_get_string (info, "X-Flatpak");
2969   if (app_id && *app_id)
2970     {
2971       ruris = g_document_portal_add_documents (uris, app_id, NULL);
2972       if (ruris == NULL)
2973         ruris = uris;
2974     }
2975 #endif
2976
2977   launch_uris_with_dbus (info, session_bus, ruris, launch_context,
2978                          cancellable, callback, user_data);
2979
2980   if (ruris != uris)
2981     g_list_free_full (ruris, g_free);
2982
2983   g_free (app_id);
2984
2985   return TRUE;
2986 }
2987
2988 static gboolean
2989 g_desktop_app_info_launch_uris_internal (GAppInfo                   *appinfo,
2990                                          GList                      *uris,
2991                                          GAppLaunchContext          *launch_context,
2992                                          GSpawnFlags                 spawn_flags,
2993                                          GSpawnChildSetupFunc        user_setup,
2994                                          gpointer                    user_setup_data,
2995                                          GDesktopAppLaunchCallback   pid_callback,
2996                                          gpointer                    pid_callback_data,
2997                                          gint                        stdin_fd,
2998                                          gint                        stdout_fd,
2999                                          gint                        stderr_fd,
3000                                          GError                     **error)
3001 {
3002   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
3003   GDBusConnection *session_bus;
3004   gboolean success = TRUE;
3005
3006   session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
3007
3008   if (session_bus && info->app_id)
3009     /* This is non-blocking API. Similar to launching via fork()/exec()
3010      * we don't wait around to see if the program crashed during startup.
3011      * This is what startup-notification's job is...
3012      */
3013     g_desktop_app_info_launch_uris_with_dbus (info, session_bus, uris, launch_context,
3014                                               NULL, NULL, NULL);
3015   else
3016     success = g_desktop_app_info_launch_uris_with_spawn (info, session_bus, info->exec, uris, launch_context,
3017                                                          spawn_flags, user_setup, user_setup_data,
3018                                                          pid_callback, pid_callback_data,
3019                                                          stdin_fd, stdout_fd, stderr_fd, error);
3020
3021   if (session_bus != NULL)
3022     {
3023       /* This asynchronous flush holds a reference until it completes,
3024        * which ensures that the following unref won't immediately kill
3025        * the connection if we were the initial owner.
3026        */
3027       g_dbus_connection_flush (session_bus, NULL, NULL, NULL);
3028       g_object_unref (session_bus);
3029     }
3030
3031   return success;
3032 }
3033
3034 static gboolean
3035 g_desktop_app_info_launch_uris (GAppInfo           *appinfo,
3036                                 GList              *uris,
3037                                 GAppLaunchContext  *launch_context,
3038                                 GError            **error)
3039 {
3040   return g_desktop_app_info_launch_uris_internal (appinfo, uris,
3041                                                   launch_context,
3042                                                   _SPAWN_FLAGS_DEFAULT,
3043                                                   NULL, NULL, NULL, NULL,
3044                                                   -1, -1, -1,
3045                                                   error);
3046 }
3047
3048 typedef struct
3049 {
3050   GAppInfo *appinfo;
3051   GList *uris;
3052   GAppLaunchContext *context;
3053 } LaunchUrisData;
3054
3055 static void
3056 launch_uris_data_free (LaunchUrisData *data)
3057 {
3058   g_clear_object (&data->context);
3059   g_list_free_full (data->uris, g_free);
3060   g_free (data);
3061 }
3062
3063 static void
3064 launch_uris_with_dbus_cb (GObject      *object,
3065                           GAsyncResult *result,
3066                           gpointer      user_data)
3067 {
3068   GTask *task = G_TASK (user_data);
3069   GError *error = NULL;
3070
3071   g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error);
3072   if (error != NULL)
3073     {
3074       g_dbus_error_strip_remote_error (error);
3075       g_task_return_error (task, g_steal_pointer (&error));
3076     }
3077   else
3078     g_task_return_boolean (task, TRUE);
3079
3080   g_object_unref (task);
3081 }
3082
3083 static void
3084 launch_uris_flush_cb (GObject      *object,
3085                       GAsyncResult *result,
3086                       gpointer      user_data)
3087 {
3088   GTask *task = G_TASK (user_data);
3089
3090   g_dbus_connection_flush_finish (G_DBUS_CONNECTION (object), result, NULL);
3091   g_task_return_boolean (task, TRUE);
3092   g_object_unref (task);
3093 }
3094
3095 static void
3096 launch_uris_bus_get_cb (GObject      *object,
3097                         GAsyncResult *result,
3098                         gpointer      user_data)
3099 {
3100   GTask *task = G_TASK (user_data);
3101   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (g_task_get_source_object (task));
3102   LaunchUrisData *data = g_task_get_task_data (task);
3103   GCancellable *cancellable = g_task_get_cancellable (task);
3104   GDBusConnection *session_bus;
3105   GError *error = NULL;
3106
3107   session_bus = g_bus_get_finish (result, NULL);
3108
3109   if (session_bus && info->app_id)
3110     {
3111       /* FIXME: The g_document_portal_add_documents() function, which is called
3112        * from the g_desktop_app_info_launch_uris_with_dbus() function, still
3113        * uses blocking calls.
3114        */
3115       g_desktop_app_info_launch_uris_with_dbus (info, session_bus,
3116                                                 data->uris, data->context,
3117                                                 cancellable,
3118                                                 launch_uris_with_dbus_cb,
3119                                                 g_steal_pointer (&task));
3120     }
3121   else
3122     {
3123       /* FIXME: The D-Bus message from the notify_desktop_launch() function
3124        * can be still lost even if flush is called later. See:
3125        * https://gitlab.freedesktop.org/dbus/dbus/issues/72
3126        */
3127       g_desktop_app_info_launch_uris_with_spawn (info, session_bus, info->exec,
3128                                                  data->uris, data->context,
3129                                                  _SPAWN_FLAGS_DEFAULT, NULL,
3130                                                  NULL, NULL, NULL, -1, -1, -1,
3131                                                  &error);
3132       if (error != NULL)
3133         {
3134           g_task_return_error (task, g_steal_pointer (&error));
3135           g_object_unref (task);
3136         }
3137       else
3138         g_dbus_connection_flush (session_bus,
3139                                  cancellable,
3140                                  launch_uris_flush_cb,
3141                                  g_steal_pointer (&task));
3142     }
3143
3144   g_clear_object (&session_bus);
3145 }
3146
3147 static void
3148 g_desktop_app_info_launch_uris_async (GAppInfo           *appinfo,
3149                                       GList              *uris,
3150                                       GAppLaunchContext  *context,
3151                                       GCancellable       *cancellable,
3152                                       GAsyncReadyCallback callback,
3153                                       gpointer            user_data)
3154 {
3155   GTask *task;
3156   LaunchUrisData *data;
3157
3158   task = g_task_new (appinfo, cancellable, callback, user_data);
3159   g_task_set_source_tag (task, g_desktop_app_info_launch_uris_async);
3160
3161   data = g_new0 (LaunchUrisData, 1);
3162   data->uris = g_list_copy_deep (uris, (GCopyFunc) g_strdup, NULL);
3163   data->context = (context != NULL) ? g_object_ref (context) : NULL;
3164   g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) launch_uris_data_free);
3165
3166   g_bus_get (G_BUS_TYPE_SESSION, cancellable, launch_uris_bus_get_cb, task);
3167 }
3168
3169 static gboolean
3170 g_desktop_app_info_launch_uris_finish (GAppInfo     *appinfo,
3171                                        GAsyncResult *result,
3172                                        GError      **error)
3173 {
3174   g_return_val_if_fail (g_task_is_valid (result, appinfo), FALSE);
3175
3176   return g_task_propagate_boolean (G_TASK (result), error);
3177 }
3178
3179 static gboolean
3180 g_desktop_app_info_supports_uris (GAppInfo *appinfo)
3181 {
3182   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
3183
3184   return info->exec &&
3185     ((strstr (info->exec, "%u") != NULL) ||
3186      (strstr (info->exec, "%U") != NULL));
3187 }
3188
3189 static gboolean
3190 g_desktop_app_info_supports_files (GAppInfo *appinfo)
3191 {
3192   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
3193
3194   return info->exec &&
3195     ((strstr (info->exec, "%f") != NULL) ||
3196      (strstr (info->exec, "%F") != NULL));
3197 }
3198
3199 static gboolean
3200 g_desktop_app_info_launch (GAppInfo           *appinfo,
3201                            GList              *files,
3202                            GAppLaunchContext  *launch_context,
3203                            GError            **error)
3204 {
3205   GList *uris;
3206   char *uri;
3207   gboolean res;
3208
3209   uris = NULL;
3210   while (files)
3211     {
3212       uri = g_file_get_uri (files->data);
3213       uris = g_list_prepend (uris, uri);
3214       files = files->next;
3215     }
3216
3217   uris = g_list_reverse (uris);
3218
3219   res = g_desktop_app_info_launch_uris (appinfo, uris, launch_context, error);
3220
3221   g_list_free_full (uris, g_free);
3222
3223   return res;
3224 }
3225
3226 /**
3227  * g_desktop_app_info_launch_uris_as_manager_with_fds:
3228  * @appinfo: a #GDesktopAppInfo
3229  * @uris: (element-type utf8): List of URIs
3230  * @launch_context: (nullable): a #GAppLaunchContext
3231  * @spawn_flags: #GSpawnFlags, used for each process
3232  * @user_setup: (scope async) (nullable): a #GSpawnChildSetupFunc, used once
3233  *     for each process.
3234  * @user_setup_data: (closure user_setup) (nullable): User data for @user_setup
3235  * @pid_callback: (scope call) (nullable): Callback for child processes
3236  * @pid_callback_data: (closure pid_callback) (nullable): User data for @callback
3237  * @stdin_fd: file descriptor to use for child's stdin, or -1
3238  * @stdout_fd: file descriptor to use for child's stdout, or -1
3239  * @stderr_fd: file descriptor to use for child's stderr, or -1
3240  * @error: return location for a #GError, or %NULL
3241  *
3242  * Equivalent to g_desktop_app_info_launch_uris_as_manager() but allows
3243  * you to pass in file descriptors for the stdin, stdout and stderr streams
3244  * of the launched process.
3245  *
3246  * If application launching occurs via some non-spawn mechanism (e.g. D-Bus
3247  * activation) then @stdin_fd, @stdout_fd and @stderr_fd are ignored.
3248  *
3249  * Returns: %TRUE on successful launch, %FALSE otherwise.
3250  *
3251  * Since: 2.58
3252  */
3253 gboolean
3254 g_desktop_app_info_launch_uris_as_manager_with_fds (GDesktopAppInfo            *appinfo,
3255                                                     GList                      *uris,
3256                                                     GAppLaunchContext          *launch_context,
3257                                                     GSpawnFlags                 spawn_flags,
3258                                                     GSpawnChildSetupFunc        user_setup,
3259                                                     gpointer                    user_setup_data,
3260                                                     GDesktopAppLaunchCallback   pid_callback,
3261                                                     gpointer                    pid_callback_data,
3262                                                     gint                        stdin_fd,
3263                                                     gint                        stdout_fd,
3264                                                     gint                        stderr_fd,
3265                                                     GError                    **error)
3266 {
3267   return g_desktop_app_info_launch_uris_internal ((GAppInfo*)appinfo,
3268                                                   uris,
3269                                                   launch_context,
3270                                                   spawn_flags,
3271                                                   user_setup,
3272                                                   user_setup_data,
3273                                                   pid_callback,
3274                                                   pid_callback_data,
3275                                                   stdin_fd,
3276                                                   stdout_fd,
3277                                                   stderr_fd,
3278                                                   error);
3279 }
3280
3281 /**
3282  * g_desktop_app_info_launch_uris_as_manager:
3283  * @appinfo: a #GDesktopAppInfo
3284  * @uris: (element-type utf8): List of URIs
3285  * @launch_context: (nullable): a #GAppLaunchContext
3286  * @spawn_flags: #GSpawnFlags, used for each process
3287  * @user_setup: (scope async) (nullable): a #GSpawnChildSetupFunc, used once
3288  *     for each process.
3289  * @user_setup_data: (closure user_setup) (nullable): User data for @user_setup
3290  * @pid_callback: (scope call) (nullable): Callback for child processes
3291  * @pid_callback_data: (closure pid_callback) (nullable): User data for @callback
3292  * @error: return location for a #GError, or %NULL
3293  *
3294  * This function performs the equivalent of g_app_info_launch_uris(),
3295  * but is intended primarily for operating system components that
3296  * launch applications.  Ordinary applications should use
3297  * g_app_info_launch_uris().
3298  *
3299  * If the application is launched via GSpawn, then @spawn_flags, @user_setup
3300  * and @user_setup_data are used for the call to g_spawn_async().
3301  * Additionally, @pid_callback (with @pid_callback_data) will be called to
3302  * inform about the PID of the created process. See g_spawn_async_with_pipes()
3303  * for information on certain parameter conditions that can enable an
3304  * optimized posix_spawn() codepath to be used.
3305  *
3306  * If application launching occurs via some other mechanism (eg: D-Bus
3307  * activation) then @spawn_flags, @user_setup, @user_setup_data,
3308  * @pid_callback and @pid_callback_data are ignored.
3309  *
3310  * Returns: %TRUE on successful launch, %FALSE otherwise.
3311  */
3312 gboolean
3313 g_desktop_app_info_launch_uris_as_manager (GDesktopAppInfo            *appinfo,
3314                                            GList                      *uris,
3315                                            GAppLaunchContext          *launch_context,
3316                                            GSpawnFlags                 spawn_flags,
3317                                            GSpawnChildSetupFunc        user_setup,
3318                                            gpointer                    user_setup_data,
3319                                            GDesktopAppLaunchCallback   pid_callback,
3320                                            gpointer                    pid_callback_data,
3321                                            GError                    **error)
3322 {
3323   return g_desktop_app_info_launch_uris_as_manager_with_fds (appinfo,
3324                                                              uris,
3325                                                              launch_context,
3326                                                              spawn_flags,
3327                                                              user_setup,
3328                                                              user_setup_data,
3329                                                              pid_callback,
3330                                                              pid_callback_data,
3331                                                              -1, -1, -1,
3332                                                              error);
3333 }
3334
3335 /* OnlyShowIn API support {{{2 */
3336
3337 /**
3338  * g_desktop_app_info_set_desktop_env:
3339  * @desktop_env: a string specifying what desktop this is
3340  *
3341  * Sets the name of the desktop that the application is running in.
3342  * This is used by g_app_info_should_show() and
3343  * g_desktop_app_info_get_show_in() to evaluate the
3344  * `OnlyShowIn` and `NotShowIn`
3345  * desktop entry fields.
3346  *
3347  * Should be called only once; subsequent calls are ignored.
3348  *
3349  * Deprecated:2.42:do not use this API.  Since 2.42 the value of the
3350  * `XDG_CURRENT_DESKTOP` environment variable will be used.
3351  */
3352 void
3353 g_desktop_app_info_set_desktop_env (const gchar *desktop_env)
3354 {
3355   get_current_desktops (desktop_env);
3356 }
3357
3358 static gboolean
3359 g_desktop_app_info_should_show (GAppInfo *appinfo)
3360 {
3361   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
3362
3363   if (info->nodisplay)
3364     return FALSE;
3365
3366   return g_desktop_app_info_get_show_in (info, NULL);
3367 }
3368
3369 /* mime types/default apps support {{{2 */
3370
3371 typedef enum {
3372   CONF_DIR,
3373   APP_DIR,
3374   MIMETYPE_DIR
3375 } DirType;
3376
3377 static char *
3378 ensure_dir (DirType   type,
3379             GError  **error)
3380 {
3381   char *path, *display_name;
3382   int errsv;
3383
3384   switch (type)
3385     {
3386     case CONF_DIR:
3387       path = g_build_filename (g_get_user_config_dir (), NULL);
3388       break;
3389
3390     case APP_DIR:
3391       path = g_build_filename (g_get_user_data_dir (), "applications", NULL);
3392       break;
3393
3394     case MIMETYPE_DIR:
3395       path = g_build_filename (g_get_user_data_dir (), "mime", "packages", NULL);
3396       break;
3397
3398     default:
3399       g_assert_not_reached ();
3400     }
3401
3402   g_debug ("%s: Ensuring %s", G_STRFUNC, path);
3403
3404   errno = 0;
3405   if (g_mkdir_with_parents (path, 0700) == 0)
3406     return path;
3407
3408   errsv = errno;
3409   display_name = g_filename_display_name (path);
3410   if (type == APP_DIR)
3411     g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
3412                  _("Can’t create user application configuration folder %s: %s"),
3413                  display_name, g_strerror (errsv));
3414   else
3415     g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
3416                  _("Can’t create user MIME configuration folder %s: %s"),
3417                  display_name, g_strerror (errsv));
3418
3419   g_free (display_name);
3420   g_free (path);
3421
3422   return NULL;
3423 }
3424
3425 static gboolean
3426 update_mimeapps_list (const char  *desktop_id,
3427                       const char  *content_type,
3428                       UpdateMimeFlags flags,
3429                       GError     **error)
3430 {
3431   char *dirname, *filename, *string;
3432   GKeyFile *key_file;
3433   gboolean load_succeeded, res;
3434   char **old_list, **list;
3435   gsize length, data_size;
3436   char *data;
3437   int i, j, k;
3438   char **content_types;
3439
3440   /* Don't add both at start and end */
3441   g_assert (!((flags & UPDATE_MIME_SET_DEFAULT) &&
3442               (flags & UPDATE_MIME_SET_NON_DEFAULT)));
3443
3444   dirname = ensure_dir (CONF_DIR, error);
3445   if (!dirname)
3446     return FALSE;
3447
3448   filename = g_build_filename (dirname, "mimeapps.list", NULL);
3449   g_free (dirname);
3450
3451   key_file = g_key_file_new ();
3452   load_succeeded = g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, NULL);
3453   if (!load_succeeded ||
3454       (!g_key_file_has_group (key_file, ADDED_ASSOCIATIONS_GROUP) &&
3455        !g_key_file_has_group (key_file, REMOVED_ASSOCIATIONS_GROUP) &&
3456        !g_key_file_has_group (key_file, DEFAULT_APPLICATIONS_GROUP)))
3457     {
3458       g_key_file_free (key_file);
3459       key_file = g_key_file_new ();
3460     }
3461
3462   if (content_type)
3463     {
3464       content_types = g_new (char *, 2);
3465       content_types[0] = g_strdup (content_type);
3466       content_types[1] = NULL;
3467     }
3468   else
3469     {
3470       content_types = g_key_file_get_keys (key_file, DEFAULT_APPLICATIONS_GROUP, NULL, NULL);
3471     }
3472
3473   for (k = 0; content_types && content_types[k]; k++)
3474     {
3475       /* set as default, if requested so */
3476       string = g_key_file_get_string (key_file,
3477                                       DEFAULT_APPLICATIONS_GROUP,
3478                                       content_types[k],
3479                                       NULL);
3480
3481       if (g_strcmp0 (string, desktop_id) != 0 &&
3482           (flags & UPDATE_MIME_SET_DEFAULT))
3483         {
3484           g_free (string);
3485           string = g_strdup (desktop_id);
3486
3487           /* add in the non-default list too, if it's not already there */
3488           flags |= UPDATE_MIME_SET_NON_DEFAULT;
3489         }
3490
3491       if (string == NULL || desktop_id == NULL)
3492         g_key_file_remove_key (key_file,
3493                                DEFAULT_APPLICATIONS_GROUP,
3494                                content_types[k],
3495                                NULL);
3496       else
3497         g_key_file_set_string (key_file,
3498                                DEFAULT_APPLICATIONS_GROUP,
3499                                content_types[k],
3500                                string);
3501
3502       g_free (string);
3503     }
3504
3505   if (content_type)
3506     {
3507       /* reuse the list from above */
3508     }
3509   else
3510     {
3511       g_strfreev (content_types);
3512       content_types = g_key_file_get_keys (key_file, ADDED_ASSOCIATIONS_GROUP, NULL, NULL);
3513     }
3514
3515   for (k = 0; content_types && content_types[k]; k++)
3516     {
3517       /* Add to the right place in the list */
3518
3519       length = 0;
3520       old_list = g_key_file_get_string_list (key_file, ADDED_ASSOCIATIONS_GROUP,
3521                                              content_types[k], &length, NULL);
3522
3523       list = g_new (char *, 1 + length + 1);
3524
3525       i = 0;
3526
3527       /* if we're adding a last-used hint, just put the application in front of the list */
3528       if (flags & UPDATE_MIME_SET_LAST_USED)
3529         {
3530           /* avoid adding this again as non-default later */
3531           if (flags & UPDATE_MIME_SET_NON_DEFAULT)
3532             flags ^= UPDATE_MIME_SET_NON_DEFAULT;
3533
3534           list[i++] = g_strdup (desktop_id);
3535         }
3536
3537       if (old_list)
3538         {
3539           for (j = 0; old_list[j] != NULL; j++)
3540             {
3541               if (g_strcmp0 (old_list[j], desktop_id) != 0)
3542                 {
3543                   /* rewrite other entries if they're different from the new one */
3544                   list[i++] = g_strdup (old_list[j]);
3545                 }
3546               else if (flags & UPDATE_MIME_SET_NON_DEFAULT)
3547                 {
3548                   /* we encountered an old entry which is equal to the one we're adding as non-default,
3549                    * don't change its position in the list.
3550                    */
3551                   flags ^= UPDATE_MIME_SET_NON_DEFAULT;
3552                   list[i++] = g_strdup (old_list[j]);
3553                 }
3554             }
3555         }
3556
3557       /* add it at the end of the list */
3558       if (flags & UPDATE_MIME_SET_NON_DEFAULT)
3559         list[i++] = g_strdup (desktop_id);
3560
3561       list[i] = NULL;
3562
3563       g_strfreev (old_list);
3564
3565       if (list[0] == NULL || desktop_id == NULL)
3566         g_key_file_remove_key (key_file,
3567                                ADDED_ASSOCIATIONS_GROUP,
3568                                content_types[k],
3569                                NULL);
3570       else
3571         g_key_file_set_string_list (key_file,
3572                                     ADDED_ASSOCIATIONS_GROUP,
3573                                     content_types[k],
3574                                     (const char * const *)list, i);
3575
3576       g_strfreev (list);
3577     }
3578
3579   if (content_type)
3580     {
3581       /* reuse the list from above */
3582     }
3583   else
3584     {
3585       g_strfreev (content_types);
3586       content_types = g_key_file_get_keys (key_file, REMOVED_ASSOCIATIONS_GROUP, NULL, NULL);
3587     }
3588
3589   for (k = 0; content_types && content_types[k]; k++)
3590     {
3591       /* Remove from removed associations group (unless remove) */
3592
3593       length = 0;
3594       old_list = g_key_file_get_string_list (key_file, REMOVED_ASSOCIATIONS_GROUP,
3595                                              content_types[k], &length, NULL);
3596
3597       list = g_new (char *, 1 + length + 1);
3598
3599       i = 0;
3600       if (flags & UPDATE_MIME_REMOVE)
3601         list[i++] = g_strdup (desktop_id);
3602       if (old_list)
3603         {
3604           for (j = 0; old_list[j] != NULL; j++)
3605             {
3606               if (g_strcmp0 (old_list[j], desktop_id) != 0)
3607                 list[i++] = g_strdup (old_list[j]);
3608             }
3609         }
3610       list[i] = NULL;
3611
3612       g_strfreev (old_list);
3613
3614       if (list[0] == NULL || desktop_id == NULL)
3615         g_key_file_remove_key (key_file,
3616                                REMOVED_ASSOCIATIONS_GROUP,
3617                                content_types[k],
3618                                NULL);
3619       else
3620         g_key_file_set_string_list (key_file,
3621                                     REMOVED_ASSOCIATIONS_GROUP,
3622                                     content_types[k],
3623                                     (const char * const *)list, i);
3624
3625       g_strfreev (list);
3626     }
3627
3628   g_strfreev (content_types);
3629
3630   data = g_key_file_to_data (key_file, &data_size, error);
3631   g_key_file_free (key_file);
3632
3633   res = g_file_set_contents (filename, data, data_size, error);
3634
3635   desktop_file_dirs_invalidate_user_config ();
3636
3637   g_free (filename);
3638   g_free (data);
3639
3640   return res;
3641 }
3642
3643 static gboolean
3644 g_desktop_app_info_set_as_last_used_for_type (GAppInfo    *appinfo,
3645                                               const char  *content_type,
3646                                               GError     **error)
3647 {
3648   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
3649
3650   if (!g_desktop_app_info_ensure_saved (info, error))
3651     return FALSE;
3652
3653   if (!info->desktop_id)
3654     {
3655       g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
3656                            _("Application information lacks an identifier"));
3657       return FALSE;
3658     }
3659
3660   /* both add support for the content type and set as last used */
3661   return update_mimeapps_list (info->desktop_id, content_type,
3662                                UPDATE_MIME_SET_NON_DEFAULT |
3663                                UPDATE_MIME_SET_LAST_USED,
3664                                error);
3665 }
3666
3667 static gboolean
3668 g_desktop_app_info_set_as_default_for_type (GAppInfo    *appinfo,
3669                                             const char  *content_type,
3670                                             GError     **error)
3671 {
3672   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
3673
3674   if (!g_desktop_app_info_ensure_saved (info, error))
3675     return FALSE;
3676
3677   if (!info->desktop_id)
3678     {
3679       g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
3680                            _("Application information lacks an identifier"));
3681       return FALSE;
3682     }
3683
3684   return update_mimeapps_list (info->desktop_id, content_type,
3685                                UPDATE_MIME_SET_DEFAULT,
3686                                error);
3687 }
3688
3689 static void
3690 update_program_done (GPid     pid,
3691                      gint     status,
3692                      gpointer data)
3693 {
3694   /* Did the application exit correctly */
3695   if (g_spawn_check_exit_status (status, NULL))
3696     {
3697       /* Here we could clean out any caches in use */
3698     }
3699 }
3700
3701 static void
3702 run_update_command (char *command,
3703                     char *subdir)
3704 {
3705         char *argv[3] = {
3706                 NULL,
3707                 NULL,
3708                 NULL,
3709         };
3710         GPid pid = 0;
3711         GError *error = NULL;
3712
3713         argv[0] = command;
3714         argv[1] = g_build_filename (g_get_user_data_dir (), subdir, NULL);
3715
3716         if (g_spawn_async ("/", argv,
3717                            NULL,       /* envp */
3718                            G_SPAWN_SEARCH_PATH |
3719                            G_SPAWN_STDOUT_TO_DEV_NULL |
3720                            G_SPAWN_STDERR_TO_DEV_NULL |
3721                            G_SPAWN_DO_NOT_REAP_CHILD,
3722                            NULL, NULL, /* No setup function */
3723                            &pid,
3724                            &error))
3725           g_child_watch_add (pid, update_program_done, NULL);
3726         else
3727           {
3728             /* If we get an error at this point, it's quite likely the user doesn't
3729              * have an installed copy of either 'update-mime-database' or
3730              * 'update-desktop-database'.  I don't think we want to popup an error
3731              * dialog at this point, so we just do a g_warning to give the user a
3732              * chance of debugging it.
3733              */
3734             g_warning ("%s", error->message);
3735             g_error_free (error);
3736           }
3737
3738         g_free (argv[1]);
3739 }
3740
3741 static gboolean
3742 g_desktop_app_info_set_as_default_for_extension (GAppInfo    *appinfo,
3743                                                  const char  *extension,
3744                                                  GError     **error)
3745 {
3746   char *filename, *basename, *mimetype;
3747   char *dirname;
3748   gboolean res;
3749
3750   if (!g_desktop_app_info_ensure_saved (G_DESKTOP_APP_INFO (appinfo), error))
3751     return FALSE;
3752
3753   dirname = ensure_dir (MIMETYPE_DIR, error);
3754   if (!dirname)
3755     return FALSE;
3756
3757   basename = g_strdup_printf ("user-extension-%s.xml", extension);
3758   filename = g_build_filename (dirname, basename, NULL);
3759   g_free (basename);
3760   g_free (dirname);
3761
3762   mimetype = g_strdup_printf ("application/x-extension-%s", extension);
3763
3764   if (!g_file_test (filename, G_FILE_TEST_EXISTS))
3765     {
3766       char *contents;
3767
3768       contents =
3769         g_strdup_printf ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
3770                          "<mime-info xmlns=\"http://www.freedesktop.org/standards/shared-mime-info\">\n"
3771                          " <mime-type type=\"%s\">\n"
3772                          "  <comment>%s document</comment>\n"
3773                          "  <glob pattern=\"*.%s\"/>\n"
3774                          " </mime-type>\n"
3775                          "</mime-info>\n", mimetype, extension, extension);
3776
3777       g_file_set_contents (filename, contents, -1, NULL);
3778       g_free (contents);
3779
3780       run_update_command ("update-mime-database", "mime");
3781     }
3782   g_free (filename);
3783
3784   res = g_desktop_app_info_set_as_default_for_type (appinfo,
3785                                                     mimetype,
3786                                                     error);
3787
3788   g_free (mimetype);
3789
3790   return res;
3791 }
3792
3793 static gboolean
3794 g_desktop_app_info_add_supports_type (GAppInfo    *appinfo,
3795                                       const char  *content_type,
3796                                       GError     **error)
3797 {
3798   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
3799
3800   if (!g_desktop_app_info_ensure_saved (G_DESKTOP_APP_INFO (info), error))
3801     return FALSE;
3802
3803   return update_mimeapps_list (info->desktop_id, content_type,
3804                                UPDATE_MIME_SET_NON_DEFAULT,
3805                                error);
3806 }
3807
3808 static gboolean
3809 g_desktop_app_info_can_remove_supports_type (GAppInfo *appinfo)
3810 {
3811   return TRUE;
3812 }
3813
3814 static gboolean
3815 g_desktop_app_info_remove_supports_type (GAppInfo    *appinfo,
3816                                          const char  *content_type,
3817                                          GError     **error)
3818 {
3819   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
3820
3821   if (!g_desktop_app_info_ensure_saved (G_DESKTOP_APP_INFO (info), error))
3822     return FALSE;
3823
3824   return update_mimeapps_list (info->desktop_id, content_type,
3825                                UPDATE_MIME_REMOVE,
3826                                error);
3827 }
3828
3829 static const char **
3830 g_desktop_app_info_get_supported_types (GAppInfo *appinfo)
3831 {
3832   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
3833
3834   return (const char**) info->mime_types;
3835 }
3836
3837 /* Saving and deleting {{{2 */
3838
3839 static gboolean
3840 g_desktop_app_info_ensure_saved (GDesktopAppInfo  *info,
3841                                  GError          **error)
3842 {
3843   GKeyFile *key_file;
3844   char *dirname;
3845   char *filename;
3846   char *data, *desktop_id;
3847   gsize data_size;
3848   int fd;
3849   gboolean res;
3850
3851   if (info->filename != NULL)
3852     return TRUE;
3853
3854   /* This is only used for object created with
3855    * g_app_info_create_from_commandline. All other
3856    * object should have a filename
3857    */
3858
3859   dirname = ensure_dir (APP_DIR, error);
3860   if (!dirname)
3861     return FALSE;
3862
3863   key_file = g_key_file_new ();
3864
3865   g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
3866                          "Encoding", "UTF-8");
3867   g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
3868                          G_KEY_FILE_DESKTOP_KEY_VERSION, "1.0");
3869   g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
3870                          G_KEY_FILE_DESKTOP_KEY_TYPE,
3871                          G_KEY_FILE_DESKTOP_TYPE_APPLICATION);
3872   if (info->terminal)
3873     g_key_file_set_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP,
3874                             G_KEY_FILE_DESKTOP_KEY_TERMINAL, TRUE);
3875   if (info->nodisplay)
3876     g_key_file_set_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP,
3877                             G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY, TRUE);
3878
3879   g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
3880                          G_KEY_FILE_DESKTOP_KEY_EXEC, info->exec);
3881
3882   g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
3883                          G_KEY_FILE_DESKTOP_KEY_NAME, info->name);
3884
3885   if (info->generic_name != NULL)
3886     g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
3887                            GENERIC_NAME_KEY, info->generic_name);
3888
3889   if (info->fullname != NULL)
3890     g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
3891                            FULL_NAME_KEY, info->fullname);
3892
3893   g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
3894                          G_KEY_FILE_DESKTOP_KEY_COMMENT, info->comment);
3895
3896   g_key_file_set_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP,
3897                           G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY, TRUE);
3898
3899   data = g_key_file_to_data (key_file, &data_size, NULL);
3900   g_key_file_free (key_file);
3901
3902   desktop_id = g_strdup_printf ("userapp-%s-XXXXXX.desktop", info->name);
3903   filename = g_build_filename (dirname, desktop_id, NULL);
3904   g_free (desktop_id);
3905   g_free (dirname);
3906
3907   fd = g_mkstemp (filename);
3908   if (fd == -1)
3909     {
3910       char *display_name;
3911
3912       display_name = g_filename_display_name (filename);
3913       g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
3914                    _("Can’t create user desktop file %s"), display_name);
3915       g_free (display_name);
3916       g_free (filename);
3917       g_free (data);
3918       return FALSE;
3919     }
3920
3921   desktop_id = g_path_get_basename (filename);
3922
3923   /* FIXME - actually handle error */
3924   (void) g_close (fd, NULL);
3925
3926   res = g_file_set_contents (filename, data, data_size, error);
3927   g_free (data);
3928   if (!res)
3929     {
3930       g_free (desktop_id);
3931       g_free (filename);
3932       return FALSE;
3933     }
3934
3935   info->filename = filename;
3936   info->desktop_id = desktop_id;
3937
3938   run_update_command ("update-desktop-database", "applications");
3939
3940   /* We just dropped a file in the user's desktop file directory.  Save
3941    * the monitor the bother of having to notice it and invalidate
3942    * immediately.
3943    *
3944    * This means that calls directly following this will be able to see
3945    * the results immediately.
3946    */
3947   desktop_file_dirs_invalidate_user_data ();
3948
3949   return TRUE;
3950 }
3951
3952 static gboolean
3953 g_desktop_app_info_can_delete (GAppInfo *appinfo)
3954 {
3955   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
3956
3957   if (info->filename)
3958     {
3959       if (strstr (info->filename, "/userapp-"))
3960         return g_access (info->filename, W_OK) == 0;
3961     }
3962
3963   return FALSE;
3964 }
3965
3966 static gboolean
3967 g_desktop_app_info_delete (GAppInfo *appinfo)
3968 {
3969   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
3970
3971   if (info->filename)
3972     {
3973       if (g_remove (info->filename) == 0)
3974         {
3975           update_mimeapps_list (info->desktop_id, NULL,
3976                                 UPDATE_MIME_NONE,
3977                                 NULL);
3978
3979           g_free (info->filename);
3980           info->filename = NULL;
3981           g_free (info->desktop_id);
3982           info->desktop_id = NULL;
3983
3984           return TRUE;
3985         }
3986     }
3987
3988   return FALSE;
3989 }
3990
3991 /* Create for commandline {{{2 */
3992 /**
3993  * g_app_info_create_from_commandline:
3994  * @commandline: (type filename): the commandline to use
3995  * @application_name: (nullable): the application name, or %NULL to use @commandline
3996  * @flags: flags that can specify details of the created #GAppInfo
3997  * @error: a #GError location to store the error occurring, %NULL to ignore.
3998  *
3999  * Creates a new #GAppInfo from the given information.
4000  *
4001  * Note that for @commandline, the quoting rules of the Exec key of the
4002  * [freedesktop.org Desktop Entry Specification](http://freedesktop.org/Standards/desktop-entry-spec)
4003  * are applied. For example, if the @commandline contains
4004  * percent-encoded URIs, the percent-character must be doubled in order to prevent it from
4005  * being swallowed by Exec key unquoting. See the specification for exact quoting rules.
4006  *
4007  * Returns: (transfer full): new #GAppInfo for given command.
4008  **/
4009 GAppInfo *
4010 g_app_info_create_from_commandline (const char           *commandline,
4011                                     const char           *application_name,
4012                                     GAppInfoCreateFlags   flags,
4013                                     GError              **error)
4014 {
4015   char **split;
4016   char *basename;
4017   GDesktopAppInfo *info;
4018
4019   g_return_val_if_fail (commandline, NULL);
4020
4021   info = g_object_new (G_TYPE_DESKTOP_APP_INFO, NULL);
4022
4023   info->filename = NULL;
4024   info->desktop_id = NULL;
4025
4026   info->terminal = (flags & G_APP_INFO_CREATE_NEEDS_TERMINAL) != 0;
4027   info->startup_notify = (flags & G_APP_INFO_CREATE_SUPPORTS_STARTUP_NOTIFICATION) != 0;
4028   info->hidden = FALSE;
4029   if ((flags & G_APP_INFO_CREATE_SUPPORTS_URIS) != 0)
4030     info->exec = g_strconcat (commandline, " %u", NULL);
4031   else
4032     info->exec = g_strconcat (commandline, " %f", NULL);
4033   info->nodisplay = TRUE;
4034   info->binary = binary_from_exec (info->exec);
4035
4036   if (application_name)
4037     info->name = g_strdup (application_name);
4038   else
4039     {
4040       /* FIXME: this should be more robust. Maybe g_shell_parse_argv and use argv[0] */
4041       split = g_strsplit (commandline, " ", 2);
4042       basename = split[0] ? g_path_get_basename (split[0]) : NULL;
4043       g_strfreev (split);
4044       info->name = basename;
4045       if (info->name == NULL)
4046         info->name = g_strdup ("custom");
4047     }
4048   info->comment = g_strdup_printf (_("Custom definition for %s"), info->name);
4049
4050   return G_APP_INFO (info);
4051 }
4052
4053 /* GAppInfo interface init */
4054
4055 static void
4056 g_desktop_app_info_iface_init (GAppInfoIface *iface)
4057 {
4058   iface->dup = g_desktop_app_info_dup;
4059   iface->equal = g_desktop_app_info_equal;
4060   iface->get_id = g_desktop_app_info_get_id;
4061   iface->get_name = g_desktop_app_info_get_name;
4062   iface->get_description = g_desktop_app_info_get_description;
4063   iface->get_executable = g_desktop_app_info_get_executable;
4064   iface->get_icon = g_desktop_app_info_get_icon;
4065   iface->launch = g_desktop_app_info_launch;
4066   iface->supports_uris = g_desktop_app_info_supports_uris;
4067   iface->supports_files = g_desktop_app_info_supports_files;
4068   iface->launch_uris = g_desktop_app_info_launch_uris;
4069   iface->launch_uris_async = g_desktop_app_info_launch_uris_async;
4070   iface->launch_uris_finish = g_desktop_app_info_launch_uris_finish;
4071   iface->should_show = g_desktop_app_info_should_show;
4072   iface->set_as_default_for_type = g_desktop_app_info_set_as_default_for_type;
4073   iface->set_as_default_for_extension = g_desktop_app_info_set_as_default_for_extension;
4074   iface->add_supports_type = g_desktop_app_info_add_supports_type;
4075   iface->can_remove_supports_type = g_desktop_app_info_can_remove_supports_type;
4076   iface->remove_supports_type = g_desktop_app_info_remove_supports_type;
4077   iface->can_delete = g_desktop_app_info_can_delete;
4078   iface->do_delete = g_desktop_app_info_delete;
4079   iface->get_commandline = g_desktop_app_info_get_commandline;
4080   iface->get_display_name = g_desktop_app_info_get_display_name;
4081   iface->set_as_last_used_for_type = g_desktop_app_info_set_as_last_used_for_type;
4082   iface->get_supported_types = g_desktop_app_info_get_supported_types;
4083 }
4084
4085 /* Recommended applications {{{2 */
4086
4087 /* Converts content_type into a list of itself with all of its parent
4088  * types (if include_fallback is enabled) or just returns a single-item
4089  * list with the unaliased content type.
4090  */
4091 static gchar **
4092 get_list_of_mimetypes (const gchar *content_type,
4093                        gboolean     include_fallback)
4094 {
4095   gchar *unaliased;
4096   GPtrArray *array;
4097
4098   array = g_ptr_array_new ();
4099   unaliased = _g_unix_content_type_unalias (content_type);
4100   g_ptr_array_add (array, unaliased);
4101
4102   if (include_fallback)
4103     {
4104       gint i;
4105
4106       /* Iterate the array as we grow it, until we have nothing more to add */
4107       for (i = 0; i < array->len; i++)
4108         {
4109           gchar **parents = _g_unix_content_type_get_parents (g_ptr_array_index (array, i));
4110           gint j;
4111
4112           for (j = 0; parents[j]; j++)
4113             /* Don't add duplicates */
4114             if (!array_contains (array, parents[j]))
4115               g_ptr_array_add (array, parents[j]);
4116             else
4117               g_free (parents[j]);
4118
4119           /* We already stole or freed each element.  Free the container. */
4120           g_free (parents);
4121         }
4122     }
4123
4124   g_ptr_array_add (array, NULL);
4125
4126   return (gchar **) g_ptr_array_free (array, FALSE);
4127 }
4128
4129 static gchar **
4130 g_desktop_app_info_get_desktop_ids_for_content_type (const gchar *content_type,
4131                                                      gboolean     include_fallback)
4132 {
4133   GPtrArray *hits, *blacklist;
4134   gchar **types;
4135   gint i, j;
4136
4137   hits = g_ptr_array_new ();
4138   blacklist = g_ptr_array_new ();
4139
4140   types = get_list_of_mimetypes (content_type, include_fallback);
4141
4142   desktop_file_dirs_lock ();
4143
4144   for (i = 0; types[i]; i++)
4145     for (j = 0; j < desktop_file_dirs->len; j++)
4146       desktop_file_dir_mime_lookup (g_ptr_array_index (desktop_file_dirs, j), types[i], hits, blacklist);
4147
4148   /* We will keep the hits past unlocking, so we must dup them */
4149   for (i = 0; i < hits->len; i++)
4150     hits->pdata[i] = g_strdup (hits->pdata[i]);
4151
4152   desktop_file_dirs_unlock ();
4153
4154   g_ptr_array_add (hits, NULL);
4155
4156   g_ptr_array_free (blacklist, TRUE);
4157   g_strfreev (types);
4158
4159   return (gchar **) g_ptr_array_free (hits, FALSE);
4160 }
4161
4162 /**
4163  * g_app_info_get_recommended_for_type:
4164  * @content_type: the content type to find a #GAppInfo for
4165  *
4166  * Gets a list of recommended #GAppInfos for a given content type, i.e.
4167  * those applications which claim to support the given content type exactly,
4168  * and not by MIME type subclassing.
4169  * Note that the first application of the list is the last used one, i.e.
4170  * the last one for which g_app_info_set_as_last_used_for_type() has been
4171  * called.
4172  *
4173  * Returns: (element-type GAppInfo) (transfer full): #GList of #GAppInfos
4174  *     for given @content_type or %NULL on error.
4175  *
4176  * Since: 2.28
4177  **/
4178 GList *
4179 g_app_info_get_recommended_for_type (const gchar *content_type)
4180 {
4181   gchar **desktop_ids;
4182   GList *infos;
4183   gint i;
4184
4185   g_return_val_if_fail (content_type != NULL, NULL);
4186
4187   desktop_ids = g_desktop_app_info_get_desktop_ids_for_content_type (content_type, FALSE);
4188
4189   infos = NULL;
4190   for (i = 0; desktop_ids[i]; i++)
4191     {
4192       GDesktopAppInfo *info;
4193
4194       info = g_desktop_app_info_new (desktop_ids[i]);
4195       if (info)
4196         infos = g_list_prepend (infos, info);
4197     }
4198
4199   g_strfreev (desktop_ids);
4200
4201   return g_list_reverse (infos);
4202 }
4203
4204 /**
4205  * g_app_info_get_fallback_for_type:
4206  * @content_type: the content type to find a #GAppInfo for
4207  *
4208  * Gets a list of fallback #GAppInfos for a given content type, i.e.
4209  * those applications which claim to support the given content type
4210  * by MIME type subclassing and not directly.
4211  *
4212  * Returns: (element-type GAppInfo) (transfer full): #GList of #GAppInfos
4213  *     for given @content_type or %NULL on error.
4214  *
4215  * Since: 2.28
4216  **/
4217 GList *
4218 g_app_info_get_fallback_for_type (const gchar *content_type)
4219 {
4220   gchar **recommended_ids;
4221   gchar **all_ids;
4222   GList *infos;
4223   gint i;
4224
4225   g_return_val_if_fail (content_type != NULL, NULL);
4226
4227   recommended_ids = g_desktop_app_info_get_desktop_ids_for_content_type (content_type, FALSE);
4228   all_ids = g_desktop_app_info_get_desktop_ids_for_content_type (content_type, TRUE);
4229
4230   infos = NULL;
4231   for (i = 0; all_ids[i]; i++)
4232     {
4233       GDesktopAppInfo *info;
4234       gint j;
4235
4236       /* Don't return the ones on the recommended list */
4237       for (j = 0; recommended_ids[j]; j++)
4238         if (g_str_equal (all_ids[i], recommended_ids[j]))
4239           break;
4240
4241       if (recommended_ids[j])
4242         continue;
4243
4244       info = g_desktop_app_info_new (all_ids[i]);
4245
4246       if (info)
4247         infos = g_list_prepend (infos, info);
4248     }
4249
4250   g_strfreev (recommended_ids);
4251   g_strfreev (all_ids);
4252
4253   return g_list_reverse (infos);
4254 }
4255
4256 /**
4257  * g_app_info_get_all_for_type:
4258  * @content_type: the content type to find a #GAppInfo for
4259  *
4260  * Gets a list of all #GAppInfos for a given content type,
4261  * including the recommended and fallback #GAppInfos. See
4262  * g_app_info_get_recommended_for_type() and
4263  * g_app_info_get_fallback_for_type().
4264  *
4265  * Returns: (element-type GAppInfo) (transfer full): #GList of #GAppInfos
4266  *     for given @content_type or %NULL on error.
4267  **/
4268 GList *
4269 g_app_info_get_all_for_type (const char *content_type)
4270 {
4271   gchar **desktop_ids;
4272   GList *infos;
4273   gint i;
4274
4275   g_return_val_if_fail (content_type != NULL, NULL);
4276
4277   desktop_ids = g_desktop_app_info_get_desktop_ids_for_content_type (content_type, TRUE);
4278
4279   infos = NULL;
4280   for (i = 0; desktop_ids[i]; i++)
4281     {
4282       GDesktopAppInfo *info;
4283
4284       info = g_desktop_app_info_new (desktop_ids[i]);
4285       if (info)
4286         infos = g_list_prepend (infos, info);
4287     }
4288
4289   g_strfreev (desktop_ids);
4290
4291   return g_list_reverse (infos);
4292 }
4293
4294 /**
4295  * g_app_info_reset_type_associations:
4296  * @content_type: a content type
4297  *
4298  * Removes all changes to the type associations done by
4299  * g_app_info_set_as_default_for_type(),
4300  * g_app_info_set_as_default_for_extension(),
4301  * g_app_info_add_supports_type() or
4302  * g_app_info_remove_supports_type().
4303  *
4304  * Since: 2.20
4305  */
4306 void
4307 g_app_info_reset_type_associations (const char *content_type)
4308 {
4309   update_mimeapps_list (NULL, content_type,
4310                         UPDATE_MIME_NONE,
4311                         NULL);
4312 }
4313
4314 /**
4315  * g_app_info_get_default_for_type:
4316  * @content_type: the content type to find a #GAppInfo for
4317  * @must_support_uris: if %TRUE, the #GAppInfo is expected to
4318  *     support URIs
4319  *
4320  * Gets the default #GAppInfo for a given content type.
4321  *
4322  * Returns: (transfer full): #GAppInfo for given @content_type or
4323  *     %NULL on error.
4324  */
4325 GAppInfo *
4326 g_app_info_get_default_for_type (const char *content_type,
4327                                  gboolean    must_support_uris)
4328 {
4329   GPtrArray *blacklist;
4330   GPtrArray *results;
4331   GAppInfo *info;
4332   gchar **types;
4333   gint i, j, k;
4334
4335   g_return_val_if_fail (content_type != NULL, NULL);
4336
4337   types = get_list_of_mimetypes (content_type, TRUE);
4338
4339   blacklist = g_ptr_array_new ();
4340   results = g_ptr_array_new ();
4341   info = NULL;
4342
4343   desktop_file_dirs_lock ();
4344
4345   for (i = 0; types[i]; i++)
4346     {
4347       /* Collect all the default apps for this type */
4348       for (j = 0; j < desktop_file_dirs->len; j++)
4349         desktop_file_dir_default_lookup (g_ptr_array_index (desktop_file_dirs, j), types[i], results);
4350
4351       /* Consider the associations as well... */
4352       for (j = 0; j < desktop_file_dirs->len; j++)
4353         desktop_file_dir_mime_lookup (g_ptr_array_index (desktop_file_dirs, j), types[i], results, blacklist);
4354
4355       /* (If any), see if one of those apps is installed... */
4356       for (j = 0; j < results->len; j++)
4357         {
4358           const gchar *desktop_id = g_ptr_array_index (results, j);
4359
4360           for (k = 0; k < desktop_file_dirs->len; k++)
4361             {
4362               info = (GAppInfo *) desktop_file_dir_get_app (g_ptr_array_index (desktop_file_dirs, k), desktop_id);
4363
4364               if (info)
4365                 {
4366                   if (!must_support_uris || g_app_info_supports_uris (info))
4367                     goto out;
4368
4369                   g_clear_object (&info);
4370                 }
4371             }
4372         }
4373
4374       /* Reset the list, ready to try again with the next (parent)
4375        * mimetype, but keep the blacklist in place.
4376        */
4377       g_ptr_array_set_size (results, 0);
4378     }
4379
4380 out:
4381   desktop_file_dirs_unlock ();
4382
4383   g_ptr_array_unref (blacklist);
4384   g_ptr_array_unref (results);
4385   g_strfreev (types);
4386
4387   return info;
4388 }
4389
4390 /**
4391  * g_app_info_get_default_for_uri_scheme:
4392  * @uri_scheme: a string containing a URI scheme.
4393  *
4394  * Gets the default application for handling URIs with
4395  * the given URI scheme. A URI scheme is the initial part
4396  * of the URI, up to but not including the ':', e.g. "http",
4397  * "ftp" or "sip".
4398  *
4399  * Returns: (transfer full): #GAppInfo for given @uri_scheme or %NULL on error.
4400  */
4401 GAppInfo *
4402 g_app_info_get_default_for_uri_scheme (const char *uri_scheme)
4403 {
4404   GAppInfo *app_info;
4405   char *content_type, *scheme_down;
4406
4407   scheme_down = g_ascii_strdown (uri_scheme, -1);
4408   content_type = g_strdup_printf ("x-scheme-handler/%s", scheme_down);
4409   g_free (scheme_down);
4410   app_info = g_app_info_get_default_for_type (content_type, FALSE);
4411   g_free (content_type);
4412
4413   return app_info;
4414 }
4415
4416 /* "Get all" API {{{2 */
4417
4418 /**
4419  * g_desktop_app_info_get_implementations:
4420  * @interface: the name of the interface
4421  *
4422  * Gets all applications that implement @interface.
4423  *
4424  * An application implements an interface if that interface is listed in
4425  * the Implements= line of the desktop file of the application.
4426  *
4427  * Returns: (element-type GDesktopAppInfo) (transfer full): a list of #GDesktopAppInfo
4428  * objects.
4429  *
4430  * Since: 2.42
4431  **/
4432 GList *
4433 g_desktop_app_info_get_implementations (const gchar *interface)
4434 {
4435   GList *result = NULL;
4436   GList **ptr;
4437   gint i;
4438
4439   desktop_file_dirs_lock ();
4440
4441   for (i = 0; i < desktop_file_dirs->len; i++)
4442     desktop_file_dir_get_implementations (g_ptr_array_index (desktop_file_dirs, i), &result, interface);
4443
4444   desktop_file_dirs_unlock ();
4445
4446   ptr = &result;
4447   while (*ptr)
4448     {
4449       gchar *name = (*ptr)->data;
4450       GDesktopAppInfo *app;
4451
4452       app = g_desktop_app_info_new (name);
4453       g_free (name);
4454
4455       if (app)
4456         {
4457           (*ptr)->data = app;
4458           ptr = &(*ptr)->next;
4459         }
4460       else
4461         *ptr = g_list_delete_link (*ptr, *ptr);
4462     }
4463
4464   return result;
4465 }
4466
4467 /**
4468  * g_desktop_app_info_search:
4469  * @search_string: the search string to use
4470  *
4471  * Searches desktop files for ones that match @search_string.
4472  *
4473  * The return value is an array of strvs.  Each strv contains a list of
4474  * applications that matched @search_string with an equal score.  The
4475  * outer list is sorted by score so that the first strv contains the
4476  * best-matching applications, and so on.
4477  * The algorithm for determining matches is undefined and may change at
4478  * any time.
4479  *
4480  * Returns: (array zero-terminated=1) (element-type GStrv) (transfer full): a
4481  *   list of strvs.  Free each item with g_strfreev() and free the outer
4482  *   list with g_free().
4483  */
4484 gchar ***
4485 g_desktop_app_info_search (const gchar *search_string)
4486 {
4487   gchar **search_tokens;
4488   gint last_category = -1;
4489   gchar ***results;
4490   gint n_categories = 0;
4491   gint start_of_category;
4492   gint i, j;
4493
4494   search_tokens = g_str_tokenize_and_fold (search_string, NULL, NULL);
4495
4496   desktop_file_dirs_lock ();
4497
4498   reset_total_search_results ();
4499
4500   for (i = 0; i < desktop_file_dirs->len; i++)
4501     {
4502       for (j = 0; search_tokens[j]; j++)
4503         {
4504           desktop_file_dir_search (g_ptr_array_index (desktop_file_dirs, i), search_tokens[j]);
4505           merge_token_results (j == 0);
4506         }
4507       merge_directory_results ();
4508     }
4509
4510   sort_total_search_results ();
4511
4512   /* Count the total number of unique categories */
4513   for (i = 0; i < static_total_results_size; i++)
4514     if (static_total_results[i].category != last_category)
4515       {
4516         last_category = static_total_results[i].category;
4517         n_categories++;
4518       }
4519
4520   results = g_new (gchar **, n_categories + 1);
4521
4522   /* Start loading into the results list */
4523   start_of_category = 0;
4524   for (i = 0; i < n_categories; i++)
4525     {
4526       gint n_items_in_category = 0;
4527       gint this_category;
4528       gint j;
4529
4530       this_category = static_total_results[start_of_category].category;
4531
4532       while (start_of_category + n_items_in_category < static_total_results_size &&
4533              static_total_results[start_of_category + n_items_in_category].category == this_category)
4534         n_items_in_category++;
4535
4536       results[i] = g_new (gchar *, n_items_in_category + 1);
4537       for (j = 0; j < n_items_in_category; j++)
4538         results[i][j] = g_strdup (static_total_results[start_of_category + j].app_name);
4539       results[i][j] = NULL;
4540
4541       start_of_category += n_items_in_category;
4542     }
4543   results[i] = NULL;
4544
4545   desktop_file_dirs_unlock ();
4546
4547   g_strfreev (search_tokens);
4548
4549   return results;
4550 }
4551
4552 /**
4553  * g_app_info_get_all:
4554  *
4555  * Gets a list of all of the applications currently registered
4556  * on this system.
4557  *
4558  * For desktop files, this includes applications that have
4559  * `NoDisplay=true` set or are excluded from display by means
4560  * of `OnlyShowIn` or `NotShowIn`. See g_app_info_should_show().
4561  * The returned list does not include applications which have
4562  * the `Hidden` key set.
4563  *
4564  * Returns: (element-type GAppInfo) (transfer full): a newly allocated #GList of references to #GAppInfos.
4565  **/
4566 GList *
4567 g_app_info_get_all (void)
4568 {
4569   GHashTable *apps;
4570   GHashTableIter iter;
4571   gpointer value;
4572   int i;
4573   GList *infos;
4574
4575   apps = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
4576
4577   desktop_file_dirs_lock ();
4578
4579   for (i = 0; i < desktop_file_dirs->len; i++)
4580     desktop_file_dir_get_all (g_ptr_array_index (desktop_file_dirs, i), apps);
4581
4582   desktop_file_dirs_unlock ();
4583
4584   infos = NULL;
4585   g_hash_table_iter_init (&iter, apps);
4586   while (g_hash_table_iter_next (&iter, NULL, &value))
4587     {
4588       if (value)
4589         infos = g_list_prepend (infos, value);
4590     }
4591
4592   g_hash_table_destroy (apps);
4593
4594   return infos;
4595 }
4596
4597 /* GDesktopAppInfoLookup interface {{{2 */
4598
4599 /**
4600  * GDesktopAppInfoLookup:
4601  *
4602  * #GDesktopAppInfoLookup is an opaque data structure and can only be accessed
4603  * using the following functions.
4604  *
4605  * Deprecated: 2.28: The #GDesktopAppInfoLookup interface is deprecated and
4606  *    unused by GIO.
4607  **/
4608
4609 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
4610
4611 typedef GDesktopAppInfoLookupIface GDesktopAppInfoLookupInterface;
4612 G_DEFINE_INTERFACE (GDesktopAppInfoLookup, g_desktop_app_info_lookup, G_TYPE_OBJECT)
4613
4614 static void
4615 g_desktop_app_info_lookup_default_init (GDesktopAppInfoLookupInterface *iface)
4616 {
4617 }
4618
4619 /* "Get for mime type" APIs {{{2 */
4620
4621 /**
4622  * g_desktop_app_info_lookup_get_default_for_uri_scheme:
4623  * @lookup: a #GDesktopAppInfoLookup
4624  * @uri_scheme: a string containing a URI scheme.
4625  *
4626  * Gets the default application for launching applications
4627  * using this URI scheme for a particular #GDesktopAppInfoLookup
4628  * implementation.
4629  *
4630  * The #GDesktopAppInfoLookup interface and this function is used
4631  * to implement g_app_info_get_default_for_uri_scheme() backends
4632  * in a GIO module. There is no reason for applications to use it
4633  * directly. Applications should use g_app_info_get_default_for_uri_scheme().
4634  *
4635  * Returns: (transfer full): #GAppInfo for given @uri_scheme or %NULL on error.
4636  *
4637  * Deprecated: 2.28: The #GDesktopAppInfoLookup interface is deprecated and
4638  *    unused by GIO.
4639  */
4640 GAppInfo *
4641 g_desktop_app_info_lookup_get_default_for_uri_scheme (GDesktopAppInfoLookup *lookup,
4642                                                       const char            *uri_scheme)
4643 {
4644   GDesktopAppInfoLookupIface *iface;
4645
4646   g_return_val_if_fail (G_IS_DESKTOP_APP_INFO_LOOKUP (lookup), NULL);
4647
4648   iface = G_DESKTOP_APP_INFO_LOOKUP_GET_IFACE (lookup);
4649
4650   return (* iface->get_default_for_uri_scheme) (lookup, uri_scheme);
4651 }
4652
4653 G_GNUC_END_IGNORE_DEPRECATIONS
4654
4655 /* Misc getter APIs {{{2 */
4656
4657 /**
4658  * g_desktop_app_info_get_startup_wm_class:
4659  * @info: a #GDesktopAppInfo that supports startup notify
4660  *
4661  * Retrieves the StartupWMClass field from @info. This represents the
4662  * WM_CLASS property of the main window of the application, if launched
4663  * through @info.
4664  *
4665  * Returns: (transfer none): the startup WM class, or %NULL if none is set
4666  * in the desktop file.
4667  *
4668  * Since: 2.34
4669  */
4670 const char *
4671 g_desktop_app_info_get_startup_wm_class (GDesktopAppInfo *info)
4672 {
4673   g_return_val_if_fail (G_IS_DESKTOP_APP_INFO (info), NULL);
4674
4675   return info->startup_wm_class;
4676 }
4677
4678 /**
4679  * g_desktop_app_info_get_string:
4680  * @info: a #GDesktopAppInfo
4681  * @key: the key to look up
4682  *
4683  * Looks up a string value in the keyfile backing @info.
4684  *
4685  * The @key is looked up in the "Desktop Entry" group.
4686  *
4687  * Returns: a newly allocated string, or %NULL if the key
4688  *     is not found
4689  *
4690  * Since: 2.36
4691  */
4692 char *
4693 g_desktop_app_info_get_string (GDesktopAppInfo *info,
4694                                const char      *key)
4695 {
4696   g_return_val_if_fail (G_IS_DESKTOP_APP_INFO (info), NULL);
4697
4698   return g_key_file_get_string (info->keyfile,
4699                                 G_KEY_FILE_DESKTOP_GROUP, key, NULL);
4700 }
4701
4702 /**
4703  * g_desktop_app_info_get_locale_string:
4704  * @info: a #GDesktopAppInfo
4705  * @key: the key to look up
4706  *
4707  * Looks up a localized string value in the keyfile backing @info
4708  * translated to the current locale.
4709  *
4710  * The @key is looked up in the "Desktop Entry" group.
4711  *
4712  * Returns: (nullable): a newly allocated string, or %NULL if the key
4713  *     is not found
4714  *
4715  * Since: 2.56
4716  */
4717 char *
4718 g_desktop_app_info_get_locale_string (GDesktopAppInfo *info,
4719                                       const char      *key)
4720 {
4721   g_return_val_if_fail (G_IS_DESKTOP_APP_INFO (info), NULL);
4722   g_return_val_if_fail (key != NULL && *key != '\0', NULL);
4723
4724   return g_key_file_get_locale_string (info->keyfile,
4725                                        G_KEY_FILE_DESKTOP_GROUP,
4726                                        key, NULL, NULL);
4727 }
4728
4729 /**
4730  * g_desktop_app_info_get_boolean:
4731  * @info: a #GDesktopAppInfo
4732  * @key: the key to look up
4733  *
4734  * Looks up a boolean value in the keyfile backing @info.
4735  *
4736  * The @key is looked up in the "Desktop Entry" group.
4737  *
4738  * Returns: the boolean value, or %FALSE if the key
4739  *     is not found
4740  *
4741  * Since: 2.36
4742  */
4743 gboolean
4744 g_desktop_app_info_get_boolean (GDesktopAppInfo *info,
4745                                 const char      *key)
4746 {
4747   g_return_val_if_fail (G_IS_DESKTOP_APP_INFO (info), FALSE);
4748
4749   return g_key_file_get_boolean (info->keyfile,
4750                                  G_KEY_FILE_DESKTOP_GROUP, key, NULL);
4751 }
4752
4753 /**
4754  * g_desktop_app_info_get_string_list:
4755  * @info: a #GDesktopAppInfo
4756  * @key: the key to look up
4757  * @length: (out) (optional): return location for the number of returned strings, or %NULL
4758  *
4759  * Looks up a string list value in the keyfile backing @info.
4760  *
4761  * The @key is looked up in the "Desktop Entry" group.
4762  *
4763  * Returns: (array zero-terminated=1 length=length) (element-type utf8) (transfer full):
4764  *  a %NULL-terminated string array or %NULL if the specified
4765  *  key cannot be found. The array should be freed with g_strfreev().
4766  *
4767  * Since: 2.60
4768  */
4769 gchar **
4770 g_desktop_app_info_get_string_list (GDesktopAppInfo *info,
4771                                     const char      *key,
4772                                     gsize           *length)
4773 {
4774   g_return_val_if_fail (G_IS_DESKTOP_APP_INFO (info), NULL);
4775
4776   return g_key_file_get_string_list (info->keyfile,
4777                                      G_KEY_FILE_DESKTOP_GROUP, key, length, NULL);
4778 }
4779
4780 /**
4781  * g_desktop_app_info_has_key:
4782  * @info: a #GDesktopAppInfo
4783  * @key: the key to look up
4784  *
4785  * Returns whether @key exists in the "Desktop Entry" group
4786  * of the keyfile backing @info.
4787  *
4788  * Returns: %TRUE if the @key exists
4789  *
4790  * Since: 2.36
4791  */
4792 gboolean
4793 g_desktop_app_info_has_key (GDesktopAppInfo *info,
4794                             const char      *key)
4795 {
4796   g_return_val_if_fail (G_IS_DESKTOP_APP_INFO (info), FALSE);
4797
4798   return g_key_file_has_key (info->keyfile,
4799                              G_KEY_FILE_DESKTOP_GROUP, key, NULL);
4800 }
4801
4802 /* Desktop actions support {{{2 */
4803
4804 /**
4805  * g_desktop_app_info_list_actions:
4806  * @info: a #GDesktopAppInfo
4807  *
4808  * Returns the list of "additional application actions" supported on the
4809  * desktop file, as per the desktop file specification.
4810  *
4811  * As per the specification, this is the list of actions that are
4812  * explicitly listed in the "Actions" key of the [Desktop Entry] group.
4813  *
4814  * Returns: (array zero-terminated=1) (element-type utf8) (transfer none): a list of strings, always non-%NULL
4815  *
4816  * Since: 2.38
4817  **/
4818 const gchar * const *
4819 g_desktop_app_info_list_actions (GDesktopAppInfo *info)
4820 {
4821   g_return_val_if_fail (G_IS_DESKTOP_APP_INFO (info), NULL);
4822
4823   return (const gchar **) info->actions;
4824 }
4825
4826 static gboolean
4827 app_info_has_action (GDesktopAppInfo *info,
4828                      const gchar     *action_name)
4829 {
4830   gint i;
4831
4832   for (i = 0; info->actions[i]; i++)
4833     if (g_str_equal (info->actions[i], action_name))
4834       return TRUE;
4835
4836   return FALSE;
4837 }
4838
4839 /**
4840  * g_desktop_app_info_get_action_name:
4841  * @info: a #GDesktopAppInfo
4842  * @action_name: the name of the action as from
4843  *   g_desktop_app_info_list_actions()
4844  *
4845  * Gets the user-visible display name of the "additional application
4846  * action" specified by @action_name.
4847  *
4848  * This corresponds to the "Name" key within the keyfile group for the
4849  * action.
4850  *
4851  * Returns: (transfer full): the locale-specific action name
4852  *
4853  * Since: 2.38
4854  */
4855 gchar *
4856 g_desktop_app_info_get_action_name (GDesktopAppInfo *info,
4857                                     const gchar     *action_name)
4858 {
4859   gchar *group_name;
4860   gchar *result;
4861
4862   g_return_val_if_fail (G_IS_DESKTOP_APP_INFO (info), NULL);
4863   g_return_val_if_fail (action_name != NULL, NULL);
4864   g_return_val_if_fail (app_info_has_action (info, action_name), NULL);
4865
4866   group_name = g_strdup_printf ("Desktop Action %s", action_name);
4867   result = g_key_file_get_locale_string (info->keyfile, group_name, "Name", NULL, NULL);
4868   g_free (group_name);
4869
4870   /* The spec says that the Name field must be given.
4871    *
4872    * If it's not, let's follow the behaviour of our get_name()
4873    * implementation above and never return %NULL.
4874    */
4875   if (result == NULL)
4876     result = g_strdup (_("Unnamed"));
4877
4878   return result;
4879 }
4880
4881 /**
4882  * g_desktop_app_info_launch_action:
4883  * @info: a #GDesktopAppInfo
4884  * @action_name: the name of the action as from
4885  *   g_desktop_app_info_list_actions()
4886  * @launch_context: (nullable): a #GAppLaunchContext
4887  *
4888  * Activates the named application action.
4889  *
4890  * You may only call this function on action names that were
4891  * returned from g_desktop_app_info_list_actions().
4892  *
4893  * Note that if the main entry of the desktop file indicates that the
4894  * application supports startup notification, and @launch_context is
4895  * non-%NULL, then startup notification will be used when activating the
4896  * action (and as such, invocation of the action on the receiving side
4897  * must signal the end of startup notification when it is completed).
4898  * This is the expected behaviour of applications declaring additional
4899  * actions, as per the desktop file specification.
4900  *
4901  * As with g_app_info_launch() there is no way to detect failures that
4902  * occur while using this function.
4903  *
4904  * Since: 2.38
4905  */
4906 void
4907 g_desktop_app_info_launch_action (GDesktopAppInfo   *info,
4908                                   const gchar       *action_name,
4909                                   GAppLaunchContext *launch_context)
4910 {
4911   GDBusConnection *session_bus;
4912
4913   g_return_if_fail (G_IS_DESKTOP_APP_INFO (info));
4914   g_return_if_fail (action_name != NULL);
4915   g_return_if_fail (app_info_has_action (info, action_name));
4916
4917   session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
4918
4919   if (session_bus && info->app_id)
4920     {
4921       gchar *object_path;
4922
4923       object_path = object_path_from_appid (info->app_id);
4924       g_dbus_connection_call (session_bus, info->app_id, object_path,
4925                               "org.freedesktop.Application", "ActivateAction",
4926                               g_variant_new ("(sav@a{sv})", action_name, NULL,
4927                                              g_desktop_app_info_make_platform_data (info, NULL, launch_context)),
4928                               NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
4929       g_free (object_path);
4930     }
4931   else
4932     {
4933       gchar *group_name;
4934       gchar *exec_line;
4935
4936       group_name = g_strdup_printf ("Desktop Action %s", action_name);
4937       exec_line = g_key_file_get_string (info->keyfile, group_name, "Exec", NULL);
4938       g_free (group_name);
4939
4940       if (exec_line)
4941         g_desktop_app_info_launch_uris_with_spawn (info, session_bus, exec_line, NULL, launch_context,
4942                                                    _SPAWN_FLAGS_DEFAULT, NULL, NULL, NULL, NULL,
4943                                                    -1, -1, -1, NULL);
4944
4945       g_free (exec_line);
4946     }
4947
4948   if (session_bus != NULL)
4949     {
4950       g_dbus_connection_flush (session_bus, NULL, NULL, NULL);
4951       g_object_unref (session_bus);
4952     }
4953 }
4954 /* Epilogue {{{1 */
4955
4956 /* vim:set foldmethod=marker: */