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