Imported Upstream version 2.59.2
[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                        GCancellable       *cancellable,
2875                        GAsyncReadyCallback callback,
2876                        gpointer            user_data)
2877 {
2878   GVariantBuilder builder;
2879   gchar *object_path;
2880
2881   g_variant_builder_init (&builder, G_VARIANT_TYPE_TUPLE);
2882
2883   if (uris)
2884     {
2885       GList *iter;
2886
2887       g_variant_builder_open (&builder, G_VARIANT_TYPE_STRING_ARRAY);
2888       for (iter = uris; iter; iter = iter->next)
2889         g_variant_builder_add (&builder, "s", iter->data);
2890       g_variant_builder_close (&builder);
2891     }
2892
2893   g_variant_builder_add_value (&builder, g_desktop_app_info_make_platform_data (info, uris, launch_context));
2894
2895   object_path = object_path_from_appid (info->app_id);
2896   g_dbus_connection_call (session_bus, info->app_id, object_path, "org.freedesktop.Application",
2897                           uris ? "Open" : "Activate", g_variant_builder_end (&builder),
2898                           NULL, G_DBUS_CALL_FLAGS_NONE, -1,
2899                           cancellable, callback, user_data);
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                                           GCancellable       *cancellable,
2909                                           GAsyncReadyCallback callback,
2910                                           gpointer            user_data)
2911 {
2912   GList *ruris = uris;
2913   char *app_id = NULL;
2914
2915   g_return_val_if_fail (info != NULL, FALSE);
2916
2917 #ifdef G_OS_UNIX
2918   app_id = g_desktop_app_info_get_string (info, "X-Flatpak");
2919   if (app_id && *app_id)
2920     {
2921       ruris = g_document_portal_add_documents (uris, app_id, NULL);
2922       if (ruris == NULL)
2923         ruris = uris;
2924     }
2925 #endif
2926
2927   launch_uris_with_dbus (info, session_bus, ruris, launch_context,
2928                          cancellable, callback, user_data);
2929
2930   if (ruris != uris)
2931     g_list_free_full (ruris, g_free);
2932
2933   g_free (app_id);
2934
2935   return TRUE;
2936 }
2937
2938 static gboolean
2939 g_desktop_app_info_launch_uris_internal (GAppInfo                   *appinfo,
2940                                          GList                      *uris,
2941                                          GAppLaunchContext          *launch_context,
2942                                          GSpawnFlags                 spawn_flags,
2943                                          GSpawnChildSetupFunc        user_setup,
2944                                          gpointer                    user_setup_data,
2945                                          GDesktopAppLaunchCallback   pid_callback,
2946                                          gpointer                    pid_callback_data,
2947                                          gint                        stdin_fd,
2948                                          gint                        stdout_fd,
2949                                          gint                        stderr_fd,
2950                                          GError                     **error)
2951 {
2952   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
2953   GDBusConnection *session_bus;
2954   gboolean success = TRUE;
2955
2956   session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
2957
2958   if (session_bus && info->app_id)
2959     /* This is non-blocking API. Similar to launching via fork()/exec()
2960      * we don't wait around to see if the program crashed during startup.
2961      * This is what startup-notification's job is...
2962      */
2963     g_desktop_app_info_launch_uris_with_dbus (info, session_bus, uris, launch_context,
2964                                               NULL, NULL, NULL);
2965   else
2966     success = g_desktop_app_info_launch_uris_with_spawn (info, session_bus, info->exec, uris, launch_context,
2967                                                          spawn_flags, user_setup, user_setup_data,
2968                                                          pid_callback, pid_callback_data,
2969                                                          stdin_fd, stdout_fd, stderr_fd, error);
2970
2971   if (session_bus != NULL)
2972     {
2973       /* This asynchronous flush holds a reference until it completes,
2974        * which ensures that the following unref won't immediately kill
2975        * the connection if we were the initial owner.
2976        */
2977       g_dbus_connection_flush (session_bus, NULL, NULL, NULL);
2978       g_object_unref (session_bus);
2979     }
2980
2981   return success;
2982 }
2983
2984 static gboolean
2985 g_desktop_app_info_launch_uris (GAppInfo           *appinfo,
2986                                 GList              *uris,
2987                                 GAppLaunchContext  *launch_context,
2988                                 GError            **error)
2989 {
2990   return g_desktop_app_info_launch_uris_internal (appinfo, uris,
2991                                                   launch_context,
2992                                                   _SPAWN_FLAGS_DEFAULT,
2993                                                   NULL, NULL, NULL, NULL,
2994                                                   -1, -1, -1,
2995                                                   error);
2996 }
2997
2998 typedef struct
2999 {
3000   GAppInfo *appinfo;
3001   GList *uris;
3002   GAppLaunchContext *context;
3003 } LaunchUrisData;
3004
3005 static void
3006 launch_uris_data_free (LaunchUrisData *data)
3007 {
3008   g_clear_object (&data->context);
3009   g_list_free_full (data->uris, g_free);
3010   g_free (data);
3011 }
3012
3013 static void
3014 launch_uris_with_dbus_cb (GObject      *object,
3015                           GAsyncResult *result,
3016                           gpointer      user_data)
3017 {
3018   GTask *task = G_TASK (user_data);
3019   GError *error = NULL;
3020
3021   g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error);
3022   if (error != NULL)
3023     {
3024       g_dbus_error_strip_remote_error (error);
3025       g_task_return_error (task, g_steal_pointer (&error));
3026     }
3027   else
3028     g_task_return_boolean (task, TRUE);
3029
3030   g_object_unref (task);
3031 }
3032
3033 static void
3034 launch_uris_flush_cb (GObject      *object,
3035                       GAsyncResult *result,
3036                       gpointer      user_data)
3037 {
3038   GTask *task = G_TASK (user_data);
3039
3040   g_dbus_connection_flush_finish (G_DBUS_CONNECTION (object), result, NULL);
3041   g_task_return_boolean (task, TRUE);
3042   g_object_unref (task);
3043 }
3044
3045 static void
3046 launch_uris_bus_get_cb (GObject      *object,
3047                         GAsyncResult *result,
3048                         gpointer      user_data)
3049 {
3050   GTask *task = G_TASK (user_data);
3051   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (g_task_get_source_object (task));
3052   LaunchUrisData *data = g_task_get_task_data (task);
3053   GCancellable *cancellable = g_task_get_cancellable (task);
3054   GDBusConnection *session_bus;
3055   GError *error = NULL;
3056
3057   session_bus = g_bus_get_finish (result, NULL);
3058
3059   if (session_bus && info->app_id)
3060     {
3061       /* FIXME: The g_document_portal_add_documents() function, which is called
3062        * from the g_desktop_app_info_launch_uris_with_dbus() function, still
3063        * uses blocking calls.
3064        */
3065       g_desktop_app_info_launch_uris_with_dbus (info, session_bus,
3066                                                 data->uris, data->context,
3067                                                 cancellable,
3068                                                 launch_uris_with_dbus_cb,
3069                                                 g_steal_pointer (&task));
3070     }
3071   else
3072     {
3073       /* FIXME: The D-Bus message from the notify_desktop_launch() function
3074        * can be still lost even if flush is called later. See:
3075        * https://gitlab.freedesktop.org/dbus/dbus/issues/72
3076        */
3077       g_desktop_app_info_launch_uris_with_spawn (info, session_bus, info->exec,
3078                                                  data->uris, data->context,
3079                                                  _SPAWN_FLAGS_DEFAULT, NULL,
3080                                                  NULL, NULL, NULL, -1, -1, -1,
3081                                                  &error);
3082       if (error != NULL)
3083         {
3084           g_task_return_error (task, g_steal_pointer (&error));
3085           g_object_unref (task);
3086         }
3087       else
3088         g_dbus_connection_flush (session_bus,
3089                                  cancellable,
3090                                  launch_uris_flush_cb,
3091                                  g_steal_pointer (&task));
3092     }
3093
3094   g_clear_object (&session_bus);
3095 }
3096
3097 static void
3098 g_desktop_app_info_launch_uris_async (GAppInfo           *appinfo,
3099                                       GList              *uris,
3100                                       GAppLaunchContext  *context,
3101                                       GCancellable       *cancellable,
3102                                       GAsyncReadyCallback callback,
3103                                       gpointer            user_data)
3104 {
3105   GTask *task;
3106   LaunchUrisData *data;
3107
3108   task = g_task_new (appinfo, cancellable, callback, user_data);
3109   g_task_set_source_tag (task, g_desktop_app_info_launch_uris_async);
3110
3111   data = g_new0 (LaunchUrisData, 1);
3112   data->uris = g_list_copy_deep (uris, (GCopyFunc) g_strdup, NULL);
3113   data->context = (context != NULL) ? g_object_ref (context) : NULL;
3114   g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) launch_uris_data_free);
3115
3116   g_bus_get (G_BUS_TYPE_SESSION, cancellable, launch_uris_bus_get_cb, task);
3117 }
3118
3119 static gboolean
3120 g_desktop_app_info_launch_uris_finish (GAppInfo     *appinfo,
3121                                        GAsyncResult *result,
3122                                        GError      **error)
3123 {
3124   g_return_val_if_fail (g_task_is_valid (result, appinfo), FALSE);
3125
3126   return g_task_propagate_boolean (G_TASK (result), error);
3127 }
3128
3129 static gboolean
3130 g_desktop_app_info_supports_uris (GAppInfo *appinfo)
3131 {
3132   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
3133
3134   return info->exec &&
3135     ((strstr (info->exec, "%u") != NULL) ||
3136      (strstr (info->exec, "%U") != NULL));
3137 }
3138
3139 static gboolean
3140 g_desktop_app_info_supports_files (GAppInfo *appinfo)
3141 {
3142   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
3143
3144   return info->exec &&
3145     ((strstr (info->exec, "%f") != NULL) ||
3146      (strstr (info->exec, "%F") != NULL));
3147 }
3148
3149 static gboolean
3150 g_desktop_app_info_launch (GAppInfo           *appinfo,
3151                            GList              *files,
3152                            GAppLaunchContext  *launch_context,
3153                            GError            **error)
3154 {
3155   GList *uris;
3156   char *uri;
3157   gboolean res;
3158
3159   uris = NULL;
3160   while (files)
3161     {
3162       uri = g_file_get_uri (files->data);
3163       uris = g_list_prepend (uris, uri);
3164       files = files->next;
3165     }
3166
3167   uris = g_list_reverse (uris);
3168
3169   res = g_desktop_app_info_launch_uris (appinfo, uris, launch_context, error);
3170
3171   g_list_free_full (uris, g_free);
3172
3173   return res;
3174 }
3175
3176 /**
3177  * g_desktop_app_info_launch_uris_as_manager_with_fds:
3178  * @appinfo: a #GDesktopAppInfo
3179  * @uris: (element-type utf8): List of URIs
3180  * @launch_context: (nullable): a #GAppLaunchContext
3181  * @spawn_flags: #GSpawnFlags, used for each process
3182  * @user_setup: (scope async) (nullable): a #GSpawnChildSetupFunc, used once
3183  *     for each process.
3184  * @user_setup_data: (closure user_setup) (nullable): User data for @user_setup
3185  * @pid_callback: (scope call) (nullable): Callback for child processes
3186  * @pid_callback_data: (closure pid_callback) (nullable): User data for @callback
3187  * @stdin_fd: file descriptor to use for child's stdin, or -1
3188  * @stdout_fd: file descriptor to use for child's stdout, or -1
3189  * @stderr_fd: file descriptor to use for child's stderr, or -1
3190  * @error: return location for a #GError, or %NULL
3191  *
3192  * Equivalent to g_desktop_app_info_launch_uris_as_manager() but allows
3193  * you to pass in file descriptors for the stdin, stdout and stderr streams
3194  * of the launched process.
3195  *
3196  * If application launching occurs via some non-spawn mechanism (e.g. D-Bus
3197  * activation) then @stdin_fd, @stdout_fd and @stderr_fd are ignored.
3198  *
3199  * Returns: %TRUE on successful launch, %FALSE otherwise.
3200  *
3201  * Since: 2.58
3202  */
3203 gboolean
3204 g_desktop_app_info_launch_uris_as_manager_with_fds (GDesktopAppInfo            *appinfo,
3205                                                     GList                      *uris,
3206                                                     GAppLaunchContext          *launch_context,
3207                                                     GSpawnFlags                 spawn_flags,
3208                                                     GSpawnChildSetupFunc        user_setup,
3209                                                     gpointer                    user_setup_data,
3210                                                     GDesktopAppLaunchCallback   pid_callback,
3211                                                     gpointer                    pid_callback_data,
3212                                                     gint                        stdin_fd,
3213                                                     gint                        stdout_fd,
3214                                                     gint                        stderr_fd,
3215                                                     GError                    **error)
3216 {
3217   return g_desktop_app_info_launch_uris_internal ((GAppInfo*)appinfo,
3218                                                   uris,
3219                                                   launch_context,
3220                                                   spawn_flags,
3221                                                   user_setup,
3222                                                   user_setup_data,
3223                                                   pid_callback,
3224                                                   pid_callback_data,
3225                                                   stdin_fd,
3226                                                   stdout_fd,
3227                                                   stderr_fd,
3228                                                   error);
3229 }
3230
3231 /**
3232  * g_desktop_app_info_launch_uris_as_manager:
3233  * @appinfo: a #GDesktopAppInfo
3234  * @uris: (element-type utf8): List of URIs
3235  * @launch_context: (nullable): a #GAppLaunchContext
3236  * @spawn_flags: #GSpawnFlags, used for each process
3237  * @user_setup: (scope async) (nullable): a #GSpawnChildSetupFunc, used once
3238  *     for each process.
3239  * @user_setup_data: (closure user_setup) (nullable): User data for @user_setup
3240  * @pid_callback: (scope call) (nullable): Callback for child processes
3241  * @pid_callback_data: (closure pid_callback) (nullable): User data for @callback
3242  * @error: return location for a #GError, or %NULL
3243  *
3244  * This function performs the equivalent of g_app_info_launch_uris(),
3245  * but is intended primarily for operating system components that
3246  * launch applications.  Ordinary applications should use
3247  * g_app_info_launch_uris().
3248  *
3249  * If the application is launched via GSpawn, then @spawn_flags, @user_setup
3250  * and @user_setup_data are used for the call to g_spawn_async().
3251  * Additionally, @pid_callback (with @pid_callback_data) will be called to
3252  * inform about the PID of the created process. See g_spawn_async_with_pipes()
3253  * for information on certain parameter conditions that can enable an
3254  * optimized posix_spawn() codepath to be used.
3255  *
3256  * If application launching occurs via some other mechanism (eg: D-Bus
3257  * activation) then @spawn_flags, @user_setup, @user_setup_data,
3258  * @pid_callback and @pid_callback_data are ignored.
3259  *
3260  * Returns: %TRUE on successful launch, %FALSE otherwise.
3261  */
3262 gboolean
3263 g_desktop_app_info_launch_uris_as_manager (GDesktopAppInfo            *appinfo,
3264                                            GList                      *uris,
3265                                            GAppLaunchContext          *launch_context,
3266                                            GSpawnFlags                 spawn_flags,
3267                                            GSpawnChildSetupFunc        user_setup,
3268                                            gpointer                    user_setup_data,
3269                                            GDesktopAppLaunchCallback   pid_callback,
3270                                            gpointer                    pid_callback_data,
3271                                            GError                    **error)
3272 {
3273   return g_desktop_app_info_launch_uris_as_manager_with_fds (appinfo,
3274                                                              uris,
3275                                                              launch_context,
3276                                                              spawn_flags,
3277                                                              user_setup,
3278                                                              user_setup_data,
3279                                                              pid_callback,
3280                                                              pid_callback_data,
3281                                                              -1, -1, -1,
3282                                                              error);
3283 }
3284
3285 /* OnlyShowIn API support {{{2 */
3286
3287 /**
3288  * g_desktop_app_info_set_desktop_env:
3289  * @desktop_env: a string specifying what desktop this is
3290  *
3291  * Sets the name of the desktop that the application is running in.
3292  * This is used by g_app_info_should_show() and
3293  * g_desktop_app_info_get_show_in() to evaluate the
3294  * `OnlyShowIn` and `NotShowIn`
3295  * desktop entry fields.
3296  *
3297  * Should be called only once; subsequent calls are ignored.
3298  *
3299  * Deprecated:2.42:do not use this API.  Since 2.42 the value of the
3300  * `XDG_CURRENT_DESKTOP` environment variable will be used.
3301  */
3302 void
3303 g_desktop_app_info_set_desktop_env (const gchar *desktop_env)
3304 {
3305   get_current_desktops (desktop_env);
3306 }
3307
3308 static gboolean
3309 g_desktop_app_info_should_show (GAppInfo *appinfo)
3310 {
3311   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
3312
3313   if (info->nodisplay)
3314     return FALSE;
3315
3316   return g_desktop_app_info_get_show_in (info, NULL);
3317 }
3318
3319 /* mime types/default apps support {{{2 */
3320
3321 typedef enum {
3322   CONF_DIR,
3323   APP_DIR,
3324   MIMETYPE_DIR
3325 } DirType;
3326
3327 static char *
3328 ensure_dir (DirType   type,
3329             GError  **error)
3330 {
3331   char *path, *display_name;
3332   int errsv;
3333
3334   switch (type)
3335     {
3336     case CONF_DIR:
3337       path = g_build_filename (g_get_user_config_dir (), NULL);
3338       break;
3339
3340     case APP_DIR:
3341       path = g_build_filename (g_get_user_data_dir (), "applications", NULL);
3342       break;
3343
3344     case MIMETYPE_DIR:
3345       path = g_build_filename (g_get_user_data_dir (), "mime", "packages", NULL);
3346       break;
3347
3348     default:
3349       g_assert_not_reached ();
3350     }
3351
3352   g_debug ("%s: Ensuring %s", G_STRFUNC, path);
3353
3354   errno = 0;
3355   if (g_mkdir_with_parents (path, 0700) == 0)
3356     return path;
3357
3358   errsv = errno;
3359   display_name = g_filename_display_name (path);
3360   if (type == APP_DIR)
3361     g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
3362                  _("Can’t create user application configuration folder %s: %s"),
3363                  display_name, g_strerror (errsv));
3364   else
3365     g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
3366                  _("Can’t create user MIME configuration folder %s: %s"),
3367                  display_name, g_strerror (errsv));
3368
3369   g_free (display_name);
3370   g_free (path);
3371
3372   return NULL;
3373 }
3374
3375 static gboolean
3376 update_mimeapps_list (const char  *desktop_id,
3377                       const char  *content_type,
3378                       UpdateMimeFlags flags,
3379                       GError     **error)
3380 {
3381   char *dirname, *filename, *string;
3382   GKeyFile *key_file;
3383   gboolean load_succeeded, res;
3384   char **old_list, **list;
3385   gsize length, data_size;
3386   char *data;
3387   int i, j, k;
3388   char **content_types;
3389
3390   /* Don't add both at start and end */
3391   g_assert (!((flags & UPDATE_MIME_SET_DEFAULT) &&
3392               (flags & UPDATE_MIME_SET_NON_DEFAULT)));
3393
3394   dirname = ensure_dir (CONF_DIR, error);
3395   if (!dirname)
3396     return FALSE;
3397
3398   filename = g_build_filename (dirname, "mimeapps.list", NULL);
3399   g_free (dirname);
3400
3401   key_file = g_key_file_new ();
3402   load_succeeded = g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, NULL);
3403   if (!load_succeeded ||
3404       (!g_key_file_has_group (key_file, ADDED_ASSOCIATIONS_GROUP) &&
3405        !g_key_file_has_group (key_file, REMOVED_ASSOCIATIONS_GROUP) &&
3406        !g_key_file_has_group (key_file, DEFAULT_APPLICATIONS_GROUP)))
3407     {
3408       g_key_file_free (key_file);
3409       key_file = g_key_file_new ();
3410     }
3411
3412   if (content_type)
3413     {
3414       content_types = g_new (char *, 2);
3415       content_types[0] = g_strdup (content_type);
3416       content_types[1] = NULL;
3417     }
3418   else
3419     {
3420       content_types = g_key_file_get_keys (key_file, DEFAULT_APPLICATIONS_GROUP, NULL, NULL);
3421     }
3422
3423   for (k = 0; content_types && content_types[k]; k++)
3424     {
3425       /* set as default, if requested so */
3426       string = g_key_file_get_string (key_file,
3427                                       DEFAULT_APPLICATIONS_GROUP,
3428                                       content_types[k],
3429                                       NULL);
3430
3431       if (g_strcmp0 (string, desktop_id) != 0 &&
3432           (flags & UPDATE_MIME_SET_DEFAULT))
3433         {
3434           g_free (string);
3435           string = g_strdup (desktop_id);
3436
3437           /* add in the non-default list too, if it's not already there */
3438           flags |= UPDATE_MIME_SET_NON_DEFAULT;
3439         }
3440
3441       if (string == NULL || desktop_id == NULL)
3442         g_key_file_remove_key (key_file,
3443                                DEFAULT_APPLICATIONS_GROUP,
3444                                content_types[k],
3445                                NULL);
3446       else
3447         g_key_file_set_string (key_file,
3448                                DEFAULT_APPLICATIONS_GROUP,
3449                                content_types[k],
3450                                string);
3451
3452       g_free (string);
3453     }
3454
3455   if (content_type)
3456     {
3457       /* reuse the list from above */
3458     }
3459   else
3460     {
3461       g_strfreev (content_types);
3462       content_types = g_key_file_get_keys (key_file, ADDED_ASSOCIATIONS_GROUP, NULL, NULL);
3463     }
3464
3465   for (k = 0; content_types && content_types[k]; k++)
3466     {
3467       /* Add to the right place in the list */
3468
3469       length = 0;
3470       old_list = g_key_file_get_string_list (key_file, ADDED_ASSOCIATIONS_GROUP,
3471                                              content_types[k], &length, NULL);
3472
3473       list = g_new (char *, 1 + length + 1);
3474
3475       i = 0;
3476
3477       /* if we're adding a last-used hint, just put the application in front of the list */
3478       if (flags & UPDATE_MIME_SET_LAST_USED)
3479         {
3480           /* avoid adding this again as non-default later */
3481           if (flags & UPDATE_MIME_SET_NON_DEFAULT)
3482             flags ^= UPDATE_MIME_SET_NON_DEFAULT;
3483
3484           list[i++] = g_strdup (desktop_id);
3485         }
3486
3487       if (old_list)
3488         {
3489           for (j = 0; old_list[j] != NULL; j++)
3490             {
3491               if (g_strcmp0 (old_list[j], desktop_id) != 0)
3492                 {
3493                   /* rewrite other entries if they're different from the new one */
3494                   list[i++] = g_strdup (old_list[j]);
3495                 }
3496               else if (flags & UPDATE_MIME_SET_NON_DEFAULT)
3497                 {
3498                   /* we encountered an old entry which is equal to the one we're adding as non-default,
3499                    * don't change its position in the list.
3500                    */
3501                   flags ^= UPDATE_MIME_SET_NON_DEFAULT;
3502                   list[i++] = g_strdup (old_list[j]);
3503                 }
3504             }
3505         }
3506
3507       /* add it at the end of the list */
3508       if (flags & UPDATE_MIME_SET_NON_DEFAULT)
3509         list[i++] = g_strdup (desktop_id);
3510
3511       list[i] = NULL;
3512
3513       g_strfreev (old_list);
3514
3515       if (list[0] == NULL || desktop_id == NULL)
3516         g_key_file_remove_key (key_file,
3517                                ADDED_ASSOCIATIONS_GROUP,
3518                                content_types[k],
3519                                NULL);
3520       else
3521         g_key_file_set_string_list (key_file,
3522                                     ADDED_ASSOCIATIONS_GROUP,
3523                                     content_types[k],
3524                                     (const char * const *)list, i);
3525
3526       g_strfreev (list);
3527     }
3528
3529   if (content_type)
3530     {
3531       /* reuse the list from above */
3532     }
3533   else
3534     {
3535       g_strfreev (content_types);
3536       content_types = g_key_file_get_keys (key_file, REMOVED_ASSOCIATIONS_GROUP, NULL, NULL);
3537     }
3538
3539   for (k = 0; content_types && content_types[k]; k++)
3540     {
3541       /* Remove from removed associations group (unless remove) */
3542
3543       length = 0;
3544       old_list = g_key_file_get_string_list (key_file, REMOVED_ASSOCIATIONS_GROUP,
3545                                              content_types[k], &length, NULL);
3546
3547       list = g_new (char *, 1 + length + 1);
3548
3549       i = 0;
3550       if (flags & UPDATE_MIME_REMOVE)
3551         list[i++] = g_strdup (desktop_id);
3552       if (old_list)
3553         {
3554           for (j = 0; old_list[j] != NULL; j++)
3555             {
3556               if (g_strcmp0 (old_list[j], desktop_id) != 0)
3557                 list[i++] = g_strdup (old_list[j]);
3558             }
3559         }
3560       list[i] = NULL;
3561
3562       g_strfreev (old_list);
3563
3564       if (list[0] == NULL || desktop_id == NULL)
3565         g_key_file_remove_key (key_file,
3566                                REMOVED_ASSOCIATIONS_GROUP,
3567                                content_types[k],
3568                                NULL);
3569       else
3570         g_key_file_set_string_list (key_file,
3571                                     REMOVED_ASSOCIATIONS_GROUP,
3572                                     content_types[k],
3573                                     (const char * const *)list, i);
3574
3575       g_strfreev (list);
3576     }
3577
3578   g_strfreev (content_types);
3579
3580   data = g_key_file_to_data (key_file, &data_size, error);
3581   g_key_file_free (key_file);
3582
3583   res = g_file_set_contents (filename, data, data_size, error);
3584
3585   desktop_file_dirs_invalidate_user_config ();
3586
3587   g_free (filename);
3588   g_free (data);
3589
3590   return res;
3591 }
3592
3593 static gboolean
3594 g_desktop_app_info_set_as_last_used_for_type (GAppInfo    *appinfo,
3595                                               const char  *content_type,
3596                                               GError     **error)
3597 {
3598   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
3599
3600   if (!g_desktop_app_info_ensure_saved (info, error))
3601     return FALSE;
3602
3603   if (!info->desktop_id)
3604     {
3605       g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
3606                            _("Application information lacks an identifier"));
3607       return FALSE;
3608     }
3609
3610   /* both add support for the content type and set as last used */
3611   return update_mimeapps_list (info->desktop_id, content_type,
3612                                UPDATE_MIME_SET_NON_DEFAULT |
3613                                UPDATE_MIME_SET_LAST_USED,
3614                                error);
3615 }
3616
3617 static gboolean
3618 g_desktop_app_info_set_as_default_for_type (GAppInfo    *appinfo,
3619                                             const char  *content_type,
3620                                             GError     **error)
3621 {
3622   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
3623
3624   if (!g_desktop_app_info_ensure_saved (info, error))
3625     return FALSE;
3626
3627   if (!info->desktop_id)
3628     {
3629       g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
3630                            _("Application information lacks an identifier"));
3631       return FALSE;
3632     }
3633
3634   return update_mimeapps_list (info->desktop_id, content_type,
3635                                UPDATE_MIME_SET_DEFAULT,
3636                                error);
3637 }
3638
3639 static void
3640 update_program_done (GPid     pid,
3641                      gint     status,
3642                      gpointer data)
3643 {
3644   /* Did the application exit correctly */
3645   if (g_spawn_check_exit_status (status, NULL))
3646     {
3647       /* Here we could clean out any caches in use */
3648     }
3649 }
3650
3651 static void
3652 run_update_command (char *command,
3653                     char *subdir)
3654 {
3655         char *argv[3] = {
3656                 NULL,
3657                 NULL,
3658                 NULL,
3659         };
3660         GPid pid = 0;
3661         GError *error = NULL;
3662
3663         argv[0] = command;
3664         argv[1] = g_build_filename (g_get_user_data_dir (), subdir, NULL);
3665
3666         if (g_spawn_async ("/", argv,
3667                            NULL,       /* envp */
3668                            G_SPAWN_SEARCH_PATH |
3669                            G_SPAWN_STDOUT_TO_DEV_NULL |
3670                            G_SPAWN_STDERR_TO_DEV_NULL |
3671                            G_SPAWN_DO_NOT_REAP_CHILD,
3672                            NULL, NULL, /* No setup function */
3673                            &pid,
3674                            &error))
3675           g_child_watch_add (pid, update_program_done, NULL);
3676         else
3677           {
3678             /* If we get an error at this point, it's quite likely the user doesn't
3679              * have an installed copy of either 'update-mime-database' or
3680              * 'update-desktop-database'.  I don't think we want to popup an error
3681              * dialog at this point, so we just do a g_warning to give the user a
3682              * chance of debugging it.
3683              */
3684             g_warning ("%s", error->message);
3685             g_error_free (error);
3686           }
3687
3688         g_free (argv[1]);
3689 }
3690
3691 static gboolean
3692 g_desktop_app_info_set_as_default_for_extension (GAppInfo    *appinfo,
3693                                                  const char  *extension,
3694                                                  GError     **error)
3695 {
3696   char *filename, *basename, *mimetype;
3697   char *dirname;
3698   gboolean res;
3699
3700   if (!g_desktop_app_info_ensure_saved (G_DESKTOP_APP_INFO (appinfo), error))
3701     return FALSE;
3702
3703   dirname = ensure_dir (MIMETYPE_DIR, error);
3704   if (!dirname)
3705     return FALSE;
3706
3707   basename = g_strdup_printf ("user-extension-%s.xml", extension);
3708   filename = g_build_filename (dirname, basename, NULL);
3709   g_free (basename);
3710   g_free (dirname);
3711
3712   mimetype = g_strdup_printf ("application/x-extension-%s", extension);
3713
3714   if (!g_file_test (filename, G_FILE_TEST_EXISTS))
3715     {
3716       char *contents;
3717
3718       contents =
3719         g_strdup_printf ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
3720                          "<mime-info xmlns=\"http://www.freedesktop.org/standards/shared-mime-info\">\n"
3721                          " <mime-type type=\"%s\">\n"
3722                          "  <comment>%s document</comment>\n"
3723                          "  <glob pattern=\"*.%s\"/>\n"
3724                          " </mime-type>\n"
3725                          "</mime-info>\n", mimetype, extension, extension);
3726
3727       g_file_set_contents (filename, contents, -1, NULL);
3728       g_free (contents);
3729
3730       run_update_command ("update-mime-database", "mime");
3731     }
3732   g_free (filename);
3733
3734   res = g_desktop_app_info_set_as_default_for_type (appinfo,
3735                                                     mimetype,
3736                                                     error);
3737
3738   g_free (mimetype);
3739
3740   return res;
3741 }
3742
3743 static gboolean
3744 g_desktop_app_info_add_supports_type (GAppInfo    *appinfo,
3745                                       const char  *content_type,
3746                                       GError     **error)
3747 {
3748   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
3749
3750   if (!g_desktop_app_info_ensure_saved (G_DESKTOP_APP_INFO (info), error))
3751     return FALSE;
3752
3753   return update_mimeapps_list (info->desktop_id, content_type,
3754                                UPDATE_MIME_SET_NON_DEFAULT,
3755                                error);
3756 }
3757
3758 static gboolean
3759 g_desktop_app_info_can_remove_supports_type (GAppInfo *appinfo)
3760 {
3761   return TRUE;
3762 }
3763
3764 static gboolean
3765 g_desktop_app_info_remove_supports_type (GAppInfo    *appinfo,
3766                                          const char  *content_type,
3767                                          GError     **error)
3768 {
3769   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
3770
3771   if (!g_desktop_app_info_ensure_saved (G_DESKTOP_APP_INFO (info), error))
3772     return FALSE;
3773
3774   return update_mimeapps_list (info->desktop_id, content_type,
3775                                UPDATE_MIME_REMOVE,
3776                                error);
3777 }
3778
3779 static const char **
3780 g_desktop_app_info_get_supported_types (GAppInfo *appinfo)
3781 {
3782   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
3783
3784   return (const char**) info->mime_types;
3785 }
3786
3787 /* Saving and deleting {{{2 */
3788
3789 static gboolean
3790 g_desktop_app_info_ensure_saved (GDesktopAppInfo  *info,
3791                                  GError          **error)
3792 {
3793   GKeyFile *key_file;
3794   char *dirname;
3795   char *filename;
3796   char *data, *desktop_id;
3797   gsize data_size;
3798   int fd;
3799   gboolean res;
3800
3801   if (info->filename != NULL)
3802     return TRUE;
3803
3804   /* This is only used for object created with
3805    * g_app_info_create_from_commandline. All other
3806    * object should have a filename
3807    */
3808
3809   dirname = ensure_dir (APP_DIR, error);
3810   if (!dirname)
3811     return FALSE;
3812
3813   key_file = g_key_file_new ();
3814
3815   g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
3816                          "Encoding", "UTF-8");
3817   g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
3818                          G_KEY_FILE_DESKTOP_KEY_VERSION, "1.0");
3819   g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
3820                          G_KEY_FILE_DESKTOP_KEY_TYPE,
3821                          G_KEY_FILE_DESKTOP_TYPE_APPLICATION);
3822   if (info->terminal)
3823     g_key_file_set_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP,
3824                             G_KEY_FILE_DESKTOP_KEY_TERMINAL, TRUE);
3825   if (info->nodisplay)
3826     g_key_file_set_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP,
3827                             G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY, TRUE);
3828
3829   g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
3830                          G_KEY_FILE_DESKTOP_KEY_EXEC, info->exec);
3831
3832   g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
3833                          G_KEY_FILE_DESKTOP_KEY_NAME, info->name);
3834
3835   if (info->generic_name != NULL)
3836     g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
3837                            GENERIC_NAME_KEY, info->generic_name);
3838
3839   if (info->fullname != NULL)
3840     g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
3841                            FULL_NAME_KEY, info->fullname);
3842
3843   g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
3844                          G_KEY_FILE_DESKTOP_KEY_COMMENT, info->comment);
3845
3846   g_key_file_set_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP,
3847                           G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY, TRUE);
3848
3849   data = g_key_file_to_data (key_file, &data_size, NULL);
3850   g_key_file_free (key_file);
3851
3852   desktop_id = g_strdup_printf ("userapp-%s-XXXXXX.desktop", info->name);
3853   filename = g_build_filename (dirname, desktop_id, NULL);
3854   g_free (desktop_id);
3855   g_free (dirname);
3856
3857   fd = g_mkstemp (filename);
3858   if (fd == -1)
3859     {
3860       char *display_name;
3861
3862       display_name = g_filename_display_name (filename);
3863       g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
3864                    _("Can’t create user desktop file %s"), display_name);
3865       g_free (display_name);
3866       g_free (filename);
3867       g_free (data);
3868       return FALSE;
3869     }
3870
3871   desktop_id = g_path_get_basename (filename);
3872
3873   /* FIXME - actually handle error */
3874   (void) g_close (fd, NULL);
3875
3876   res = g_file_set_contents (filename, data, data_size, error);
3877   g_free (data);
3878   if (!res)
3879     {
3880       g_free (desktop_id);
3881       g_free (filename);
3882       return FALSE;
3883     }
3884
3885   info->filename = filename;
3886   info->desktop_id = desktop_id;
3887
3888   run_update_command ("update-desktop-database", "applications");
3889
3890   /* We just dropped a file in the user's desktop file directory.  Save
3891    * the monitor the bother of having to notice it and invalidate
3892    * immediately.
3893    *
3894    * This means that calls directly following this will be able to see
3895    * the results immediately.
3896    */
3897   desktop_file_dirs_invalidate_user_data ();
3898
3899   return TRUE;
3900 }
3901
3902 static gboolean
3903 g_desktop_app_info_can_delete (GAppInfo *appinfo)
3904 {
3905   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
3906
3907   if (info->filename)
3908     {
3909       if (strstr (info->filename, "/userapp-"))
3910         return g_access (info->filename, W_OK) == 0;
3911     }
3912
3913   return FALSE;
3914 }
3915
3916 static gboolean
3917 g_desktop_app_info_delete (GAppInfo *appinfo)
3918 {
3919   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
3920
3921   if (info->filename)
3922     {
3923       if (g_remove (info->filename) == 0)
3924         {
3925           update_mimeapps_list (info->desktop_id, NULL,
3926                                 UPDATE_MIME_NONE,
3927                                 NULL);
3928
3929           g_free (info->filename);
3930           info->filename = NULL;
3931           g_free (info->desktop_id);
3932           info->desktop_id = NULL;
3933
3934           return TRUE;
3935         }
3936     }
3937
3938   return FALSE;
3939 }
3940
3941 /* Create for commandline {{{2 */
3942 /**
3943  * g_app_info_create_from_commandline:
3944  * @commandline: (type filename): the commandline to use
3945  * @application_name: (nullable): the application name, or %NULL to use @commandline
3946  * @flags: flags that can specify details of the created #GAppInfo
3947  * @error: a #GError location to store the error occurring, %NULL to ignore.
3948  *
3949  * Creates a new #GAppInfo from the given information.
3950  *
3951  * Note that for @commandline, the quoting rules of the Exec key of the
3952  * [freedesktop.org Desktop Entry Specification](http://freedesktop.org/Standards/desktop-entry-spec)
3953  * are applied. For example, if the @commandline contains
3954  * percent-encoded URIs, the percent-character must be doubled in order to prevent it from
3955  * being swallowed by Exec key unquoting. See the specification for exact quoting rules.
3956  *
3957  * Returns: (transfer full): new #GAppInfo for given command.
3958  **/
3959 GAppInfo *
3960 g_app_info_create_from_commandline (const char           *commandline,
3961                                     const char           *application_name,
3962                                     GAppInfoCreateFlags   flags,
3963                                     GError              **error)
3964 {
3965   char **split;
3966   char *basename;
3967   GDesktopAppInfo *info;
3968
3969   g_return_val_if_fail (commandline, NULL);
3970
3971   info = g_object_new (G_TYPE_DESKTOP_APP_INFO, NULL);
3972
3973   info->filename = NULL;
3974   info->desktop_id = NULL;
3975
3976   info->terminal = (flags & G_APP_INFO_CREATE_NEEDS_TERMINAL) != 0;
3977   info->startup_notify = (flags & G_APP_INFO_CREATE_SUPPORTS_STARTUP_NOTIFICATION) != 0;
3978   info->hidden = FALSE;
3979   if ((flags & G_APP_INFO_CREATE_SUPPORTS_URIS) != 0)
3980     info->exec = g_strconcat (commandline, " %u", NULL);
3981   else
3982     info->exec = g_strconcat (commandline, " %f", NULL);
3983   info->nodisplay = TRUE;
3984   info->binary = binary_from_exec (info->exec);
3985
3986   if (application_name)
3987     info->name = g_strdup (application_name);
3988   else
3989     {
3990       /* FIXME: this should be more robust. Maybe g_shell_parse_argv and use argv[0] */
3991       split = g_strsplit (commandline, " ", 2);
3992       basename = split[0] ? g_path_get_basename (split[0]) : NULL;
3993       g_strfreev (split);
3994       info->name = basename;
3995       if (info->name == NULL)
3996         info->name = g_strdup ("custom");
3997     }
3998   info->comment = g_strdup_printf (_("Custom definition for %s"), info->name);
3999
4000   return G_APP_INFO (info);
4001 }
4002
4003 /* GAppInfo interface init */
4004
4005 static void
4006 g_desktop_app_info_iface_init (GAppInfoIface *iface)
4007 {
4008   iface->dup = g_desktop_app_info_dup;
4009   iface->equal = g_desktop_app_info_equal;
4010   iface->get_id = g_desktop_app_info_get_id;
4011   iface->get_name = g_desktop_app_info_get_name;
4012   iface->get_description = g_desktop_app_info_get_description;
4013   iface->get_executable = g_desktop_app_info_get_executable;
4014   iface->get_icon = g_desktop_app_info_get_icon;
4015   iface->launch = g_desktop_app_info_launch;
4016   iface->supports_uris = g_desktop_app_info_supports_uris;
4017   iface->supports_files = g_desktop_app_info_supports_files;
4018   iface->launch_uris = g_desktop_app_info_launch_uris;
4019   iface->launch_uris_async = g_desktop_app_info_launch_uris_async;
4020   iface->launch_uris_finish = g_desktop_app_info_launch_uris_finish;
4021   iface->should_show = g_desktop_app_info_should_show;
4022   iface->set_as_default_for_type = g_desktop_app_info_set_as_default_for_type;
4023   iface->set_as_default_for_extension = g_desktop_app_info_set_as_default_for_extension;
4024   iface->add_supports_type = g_desktop_app_info_add_supports_type;
4025   iface->can_remove_supports_type = g_desktop_app_info_can_remove_supports_type;
4026   iface->remove_supports_type = g_desktop_app_info_remove_supports_type;
4027   iface->can_delete = g_desktop_app_info_can_delete;
4028   iface->do_delete = g_desktop_app_info_delete;
4029   iface->get_commandline = g_desktop_app_info_get_commandline;
4030   iface->get_display_name = g_desktop_app_info_get_display_name;
4031   iface->set_as_last_used_for_type = g_desktop_app_info_set_as_last_used_for_type;
4032   iface->get_supported_types = g_desktop_app_info_get_supported_types;
4033 }
4034
4035 /* Recommended applications {{{2 */
4036
4037 /* Converts content_type into a list of itself with all of its parent
4038  * types (if include_fallback is enabled) or just returns a single-item
4039  * list with the unaliased content type.
4040  */
4041 static gchar **
4042 get_list_of_mimetypes (const gchar *content_type,
4043                        gboolean     include_fallback)
4044 {
4045   gchar *unaliased;
4046   GPtrArray *array;
4047
4048   array = g_ptr_array_new ();
4049   unaliased = _g_unix_content_type_unalias (content_type);
4050   g_ptr_array_add (array, unaliased);
4051
4052   if (include_fallback)
4053     {
4054       gint i;
4055
4056       /* Iterate the array as we grow it, until we have nothing more to add */
4057       for (i = 0; i < array->len; i++)
4058         {
4059           gchar **parents = _g_unix_content_type_get_parents (g_ptr_array_index (array, i));
4060           gint j;
4061
4062           for (j = 0; parents[j]; j++)
4063             /* Don't add duplicates */
4064             if (!array_contains (array, parents[j]))
4065               g_ptr_array_add (array, parents[j]);
4066             else
4067               g_free (parents[j]);
4068
4069           /* We already stole or freed each element.  Free the container. */
4070           g_free (parents);
4071         }
4072     }
4073
4074   g_ptr_array_add (array, NULL);
4075
4076   return (gchar **) g_ptr_array_free (array, FALSE);
4077 }
4078
4079 static gchar **
4080 g_desktop_app_info_get_desktop_ids_for_content_type (const gchar *content_type,
4081                                                      gboolean     include_fallback)
4082 {
4083   GPtrArray *hits, *blacklist;
4084   gchar **types;
4085   gint i, j;
4086
4087   hits = g_ptr_array_new ();
4088   blacklist = g_ptr_array_new ();
4089
4090   types = get_list_of_mimetypes (content_type, include_fallback);
4091
4092   desktop_file_dirs_lock ();
4093
4094   for (i = 0; types[i]; i++)
4095     for (j = 0; j < n_desktop_file_dirs; j++)
4096       desktop_file_dir_mime_lookup (&desktop_file_dirs[j], types[i], hits, blacklist);
4097
4098   /* We will keep the hits past unlocking, so we must dup them */
4099   for (i = 0; i < hits->len; i++)
4100     hits->pdata[i] = g_strdup (hits->pdata[i]);
4101
4102   desktop_file_dirs_unlock ();
4103
4104   g_ptr_array_add (hits, NULL);
4105
4106   g_ptr_array_free (blacklist, TRUE);
4107   g_strfreev (types);
4108
4109   return (gchar **) g_ptr_array_free (hits, FALSE);
4110 }
4111
4112 /**
4113  * g_app_info_get_recommended_for_type:
4114  * @content_type: the content type to find a #GAppInfo for
4115  *
4116  * Gets a list of recommended #GAppInfos for a given content type, i.e.
4117  * those applications which claim to support the given content type exactly,
4118  * and not by MIME type subclassing.
4119  * Note that the first application of the list is the last used one, i.e.
4120  * the last one for which g_app_info_set_as_last_used_for_type() has been
4121  * called.
4122  *
4123  * Returns: (element-type GAppInfo) (transfer full): #GList of #GAppInfos
4124  *     for given @content_type or %NULL on error.
4125  *
4126  * Since: 2.28
4127  **/
4128 GList *
4129 g_app_info_get_recommended_for_type (const gchar *content_type)
4130 {
4131   gchar **desktop_ids;
4132   GList *infos;
4133   gint i;
4134
4135   g_return_val_if_fail (content_type != NULL, NULL);
4136
4137   desktop_ids = g_desktop_app_info_get_desktop_ids_for_content_type (content_type, FALSE);
4138
4139   infos = NULL;
4140   for (i = 0; desktop_ids[i]; i++)
4141     {
4142       GDesktopAppInfo *info;
4143
4144       info = g_desktop_app_info_new (desktop_ids[i]);
4145       if (info)
4146         infos = g_list_prepend (infos, info);
4147     }
4148
4149   g_strfreev (desktop_ids);
4150
4151   return g_list_reverse (infos);
4152 }
4153
4154 /**
4155  * g_app_info_get_fallback_for_type:
4156  * @content_type: the content type to find a #GAppInfo for
4157  *
4158  * Gets a list of fallback #GAppInfos for a given content type, i.e.
4159  * those applications which claim to support the given content type
4160  * by MIME type subclassing and not directly.
4161  *
4162  * Returns: (element-type GAppInfo) (transfer full): #GList of #GAppInfos
4163  *     for given @content_type or %NULL on error.
4164  *
4165  * Since: 2.28
4166  **/
4167 GList *
4168 g_app_info_get_fallback_for_type (const gchar *content_type)
4169 {
4170   gchar **recommended_ids;
4171   gchar **all_ids;
4172   GList *infos;
4173   gint i;
4174
4175   g_return_val_if_fail (content_type != NULL, NULL);
4176
4177   recommended_ids = g_desktop_app_info_get_desktop_ids_for_content_type (content_type, FALSE);
4178   all_ids = g_desktop_app_info_get_desktop_ids_for_content_type (content_type, TRUE);
4179
4180   infos = NULL;
4181   for (i = 0; all_ids[i]; i++)
4182     {
4183       GDesktopAppInfo *info;
4184       gint j;
4185
4186       /* Don't return the ones on the recommended list */
4187       for (j = 0; recommended_ids[j]; j++)
4188         if (g_str_equal (all_ids[i], recommended_ids[j]))
4189           break;
4190
4191       if (recommended_ids[j])
4192         continue;
4193
4194       info = g_desktop_app_info_new (all_ids[i]);
4195
4196       if (info)
4197         infos = g_list_prepend (infos, info);
4198     }
4199
4200   g_strfreev (recommended_ids);
4201   g_strfreev (all_ids);
4202
4203   return g_list_reverse (infos);
4204 }
4205
4206 /**
4207  * g_app_info_get_all_for_type:
4208  * @content_type: the content type to find a #GAppInfo for
4209  *
4210  * Gets a list of all #GAppInfos for a given content type,
4211  * including the recommended and fallback #GAppInfos. See
4212  * g_app_info_get_recommended_for_type() and
4213  * g_app_info_get_fallback_for_type().
4214  *
4215  * Returns: (element-type GAppInfo) (transfer full): #GList of #GAppInfos
4216  *     for given @content_type or %NULL on error.
4217  **/
4218 GList *
4219 g_app_info_get_all_for_type (const char *content_type)
4220 {
4221   gchar **desktop_ids;
4222   GList *infos;
4223   gint i;
4224
4225   g_return_val_if_fail (content_type != NULL, NULL);
4226
4227   desktop_ids = g_desktop_app_info_get_desktop_ids_for_content_type (content_type, TRUE);
4228
4229   infos = NULL;
4230   for (i = 0; desktop_ids[i]; i++)
4231     {
4232       GDesktopAppInfo *info;
4233
4234       info = g_desktop_app_info_new (desktop_ids[i]);
4235       if (info)
4236         infos = g_list_prepend (infos, info);
4237     }
4238
4239   g_strfreev (desktop_ids);
4240
4241   return g_list_reverse (infos);
4242 }
4243
4244 /**
4245  * g_app_info_reset_type_associations:
4246  * @content_type: a content type
4247  *
4248  * Removes all changes to the type associations done by
4249  * g_app_info_set_as_default_for_type(),
4250  * g_app_info_set_as_default_for_extension(),
4251  * g_app_info_add_supports_type() or
4252  * g_app_info_remove_supports_type().
4253  *
4254  * Since: 2.20
4255  */
4256 void
4257 g_app_info_reset_type_associations (const char *content_type)
4258 {
4259   update_mimeapps_list (NULL, content_type,
4260                         UPDATE_MIME_NONE,
4261                         NULL);
4262 }
4263
4264 /**
4265  * g_app_info_get_default_for_type:
4266  * @content_type: the content type to find a #GAppInfo for
4267  * @must_support_uris: if %TRUE, the #GAppInfo is expected to
4268  *     support URIs
4269  *
4270  * Gets the default #GAppInfo for a given content type.
4271  *
4272  * Returns: (transfer full): #GAppInfo for given @content_type or
4273  *     %NULL on error.
4274  */
4275 GAppInfo *
4276 g_app_info_get_default_for_type (const char *content_type,
4277                                  gboolean    must_support_uris)
4278 {
4279   GPtrArray *blacklist;
4280   GPtrArray *results;
4281   GAppInfo *info;
4282   gchar **types;
4283   gint i, j, k;
4284
4285   g_return_val_if_fail (content_type != NULL, NULL);
4286
4287   types = get_list_of_mimetypes (content_type, TRUE);
4288
4289   blacklist = g_ptr_array_new ();
4290   results = g_ptr_array_new ();
4291   info = NULL;
4292
4293   desktop_file_dirs_lock ();
4294
4295   for (i = 0; types[i]; i++)
4296     {
4297       /* Collect all the default apps for this type */
4298       for (j = 0; j < n_desktop_file_dirs; j++)
4299         desktop_file_dir_default_lookup (&desktop_file_dirs[j], types[i], results);
4300
4301       /* Consider the associations as well... */
4302       for (j = 0; j < n_desktop_file_dirs; j++)
4303         desktop_file_dir_mime_lookup (&desktop_file_dirs[j], types[i], results, blacklist);
4304
4305       /* (If any), see if one of those apps is installed... */
4306       for (j = 0; j < results->len; j++)
4307         {
4308           const gchar *desktop_id = g_ptr_array_index (results, j);
4309
4310           for (k = 0; k < n_desktop_file_dirs; k++)
4311             {
4312               info = (GAppInfo *) desktop_file_dir_get_app (&desktop_file_dirs[k], desktop_id);
4313
4314               if (info)
4315                 {
4316                   if (!must_support_uris || g_app_info_supports_uris (info))
4317                     goto out;
4318
4319                   g_clear_object (&info);
4320                 }
4321             }
4322         }
4323
4324       /* Reset the list, ready to try again with the next (parent)
4325        * mimetype, but keep the blacklist in place.
4326        */
4327       g_ptr_array_set_size (results, 0);
4328     }
4329
4330 out:
4331   desktop_file_dirs_unlock ();
4332
4333   g_ptr_array_unref (blacklist);
4334   g_ptr_array_unref (results);
4335   g_strfreev (types);
4336
4337   return info;
4338 }
4339
4340 /**
4341  * g_app_info_get_default_for_uri_scheme:
4342  * @uri_scheme: a string containing a URI scheme.
4343  *
4344  * Gets the default application for handling URIs with
4345  * the given URI scheme. A URI scheme is the initial part
4346  * of the URI, up to but not including the ':', e.g. "http",
4347  * "ftp" or "sip".
4348  *
4349  * Returns: (transfer full): #GAppInfo for given @uri_scheme or %NULL on error.
4350  */
4351 GAppInfo *
4352 g_app_info_get_default_for_uri_scheme (const char *uri_scheme)
4353 {
4354   GAppInfo *app_info;
4355   char *content_type, *scheme_down;
4356
4357   scheme_down = g_ascii_strdown (uri_scheme, -1);
4358   content_type = g_strdup_printf ("x-scheme-handler/%s", scheme_down);
4359   g_free (scheme_down);
4360   app_info = g_app_info_get_default_for_type (content_type, FALSE);
4361   g_free (content_type);
4362
4363   return app_info;
4364 }
4365
4366 /* "Get all" API {{{2 */
4367
4368 /**
4369  * g_desktop_app_info_get_implementations:
4370  * @interface: the name of the interface
4371  *
4372  * Gets all applications that implement @interface.
4373  *
4374  * An application implements an interface if that interface is listed in
4375  * the Implements= line of the desktop file of the application.
4376  *
4377  * Returns: (element-type GDesktopAppInfo) (transfer full): a list of #GDesktopAppInfo
4378  * objects.
4379  *
4380  * Since: 2.42
4381  **/
4382 GList *
4383 g_desktop_app_info_get_implementations (const gchar *interface)
4384 {
4385   GList *result = NULL;
4386   GList **ptr;
4387   gint i;
4388
4389   desktop_file_dirs_lock ();
4390
4391   for (i = 0; i < n_desktop_file_dirs; i++)
4392     desktop_file_dir_get_implementations (&desktop_file_dirs[i], &result, interface);
4393
4394   desktop_file_dirs_unlock ();
4395
4396   ptr = &result;
4397   while (*ptr)
4398     {
4399       gchar *name = (*ptr)->data;
4400       GDesktopAppInfo *app;
4401
4402       app = g_desktop_app_info_new (name);
4403       g_free (name);
4404
4405       if (app)
4406         {
4407           (*ptr)->data = app;
4408           ptr = &(*ptr)->next;
4409         }
4410       else
4411         *ptr = g_list_delete_link (*ptr, *ptr);
4412     }
4413
4414   return result;
4415 }
4416
4417 /**
4418  * g_desktop_app_info_search:
4419  * @search_string: the search string to use
4420  *
4421  * Searches desktop files for ones that match @search_string.
4422  *
4423  * The return value is an array of strvs.  Each strv contains a list of
4424  * applications that matched @search_string with an equal score.  The
4425  * outer list is sorted by score so that the first strv contains the
4426  * best-matching applications, and so on.
4427  * The algorithm for determining matches is undefined and may change at
4428  * any time.
4429  *
4430  * Returns: (array zero-terminated=1) (element-type GStrv) (transfer full): a
4431  *   list of strvs.  Free each item with g_strfreev() and free the outer
4432  *   list with g_free().
4433  */
4434 gchar ***
4435 g_desktop_app_info_search (const gchar *search_string)
4436 {
4437   gchar **search_tokens;
4438   gint last_category = -1;
4439   gchar ***results;
4440   gint n_categories = 0;
4441   gint start_of_category;
4442   gint i, j;
4443
4444   search_tokens = g_str_tokenize_and_fold (search_string, NULL, NULL);
4445
4446   desktop_file_dirs_lock ();
4447
4448   reset_total_search_results ();
4449
4450   for (i = 0; i < n_desktop_file_dirs; i++)
4451     {
4452       for (j = 0; search_tokens[j]; j++)
4453         {
4454           desktop_file_dir_search (&desktop_file_dirs[i], search_tokens[j]);
4455           merge_token_results (j == 0);
4456         }
4457       merge_directory_results ();
4458     }
4459
4460   sort_total_search_results ();
4461
4462   /* Count the total number of unique categories */
4463   for (i = 0; i < static_total_results_size; i++)
4464     if (static_total_results[i].category != last_category)
4465       {
4466         last_category = static_total_results[i].category;
4467         n_categories++;
4468       }
4469
4470   results = g_new (gchar **, n_categories + 1);
4471
4472   /* Start loading into the results list */
4473   start_of_category = 0;
4474   for (i = 0; i < n_categories; i++)
4475     {
4476       gint n_items_in_category = 0;
4477       gint this_category;
4478       gint j;
4479
4480       this_category = static_total_results[start_of_category].category;
4481
4482       while (start_of_category + n_items_in_category < static_total_results_size &&
4483              static_total_results[start_of_category + n_items_in_category].category == this_category)
4484         n_items_in_category++;
4485
4486       results[i] = g_new (gchar *, n_items_in_category + 1);
4487       for (j = 0; j < n_items_in_category; j++)
4488         results[i][j] = g_strdup (static_total_results[start_of_category + j].app_name);
4489       results[i][j] = NULL;
4490
4491       start_of_category += n_items_in_category;
4492     }
4493   results[i] = NULL;
4494
4495   desktop_file_dirs_unlock ();
4496
4497   g_strfreev (search_tokens);
4498
4499   return results;
4500 }
4501
4502 /**
4503  * g_app_info_get_all:
4504  *
4505  * Gets a list of all of the applications currently registered
4506  * on this system.
4507  *
4508  * For desktop files, this includes applications that have
4509  * `NoDisplay=true` set or are excluded from display by means
4510  * of `OnlyShowIn` or `NotShowIn`. See g_app_info_should_show().
4511  * The returned list does not include applications which have
4512  * the `Hidden` key set.
4513  *
4514  * Returns: (element-type GAppInfo) (transfer full): a newly allocated #GList of references to #GAppInfos.
4515  **/
4516 GList *
4517 g_app_info_get_all (void)
4518 {
4519   GHashTable *apps;
4520   GHashTableIter iter;
4521   gpointer value;
4522   int i;
4523   GList *infos;
4524
4525   apps = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
4526
4527   desktop_file_dirs_lock ();
4528
4529   for (i = 0; i < n_desktop_file_dirs; i++)
4530     desktop_file_dir_get_all (&desktop_file_dirs[i], apps);
4531
4532   desktop_file_dirs_unlock ();
4533
4534   infos = NULL;
4535   g_hash_table_iter_init (&iter, apps);
4536   while (g_hash_table_iter_next (&iter, NULL, &value))
4537     {
4538       if (value)
4539         infos = g_list_prepend (infos, value);
4540     }
4541
4542   g_hash_table_destroy (apps);
4543
4544   return infos;
4545 }
4546
4547 /* GDesktopAppInfoLookup interface {{{2 */
4548
4549 /**
4550  * GDesktopAppInfoLookup:
4551  *
4552  * #GDesktopAppInfoLookup is an opaque data structure and can only be accessed
4553  * using the following functions.
4554  **/
4555
4556 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
4557
4558 typedef GDesktopAppInfoLookupIface GDesktopAppInfoLookupInterface;
4559 G_DEFINE_INTERFACE (GDesktopAppInfoLookup, g_desktop_app_info_lookup, G_TYPE_OBJECT)
4560
4561 static void
4562 g_desktop_app_info_lookup_default_init (GDesktopAppInfoLookupInterface *iface)
4563 {
4564 }
4565
4566 /* "Get for mime type" APIs {{{2 */
4567
4568 /**
4569  * g_desktop_app_info_lookup_get_default_for_uri_scheme:
4570  * @lookup: a #GDesktopAppInfoLookup
4571  * @uri_scheme: a string containing a URI scheme.
4572  *
4573  * Gets the default application for launching applications
4574  * using this URI scheme for a particular GDesktopAppInfoLookup
4575  * implementation.
4576  *
4577  * The GDesktopAppInfoLookup interface and this function is used
4578  * to implement g_app_info_get_default_for_uri_scheme() backends
4579  * in a GIO module. There is no reason for applications to use it
4580  * directly. Applications should use g_app_info_get_default_for_uri_scheme().
4581  *
4582  * Returns: (transfer full): #GAppInfo for given @uri_scheme or %NULL on error.
4583  *
4584  * Deprecated: The #GDesktopAppInfoLookup interface is deprecated and unused by gio.
4585  */
4586 GAppInfo *
4587 g_desktop_app_info_lookup_get_default_for_uri_scheme (GDesktopAppInfoLookup *lookup,
4588                                                       const char            *uri_scheme)
4589 {
4590   GDesktopAppInfoLookupIface *iface;
4591
4592   g_return_val_if_fail (G_IS_DESKTOP_APP_INFO_LOOKUP (lookup), NULL);
4593
4594   iface = G_DESKTOP_APP_INFO_LOOKUP_GET_IFACE (lookup);
4595
4596   return (* iface->get_default_for_uri_scheme) (lookup, uri_scheme);
4597 }
4598
4599 G_GNUC_END_IGNORE_DEPRECATIONS
4600
4601 /* Misc getter APIs {{{2 */
4602
4603 /**
4604  * g_desktop_app_info_get_startup_wm_class:
4605  * @info: a #GDesktopAppInfo that supports startup notify
4606  *
4607  * Retrieves the StartupWMClass field from @info. This represents the
4608  * WM_CLASS property of the main window of the application, if launched
4609  * through @info.
4610  *
4611  * Returns: (transfer none): the startup WM class, or %NULL if none is set
4612  * in the desktop file.
4613  *
4614  * Since: 2.34
4615  */
4616 const char *
4617 g_desktop_app_info_get_startup_wm_class (GDesktopAppInfo *info)
4618 {
4619   g_return_val_if_fail (G_IS_DESKTOP_APP_INFO (info), NULL);
4620
4621   return info->startup_wm_class;
4622 }
4623
4624 /**
4625  * g_desktop_app_info_get_string:
4626  * @info: a #GDesktopAppInfo
4627  * @key: the key to look up
4628  *
4629  * Looks up a string value in the keyfile backing @info.
4630  *
4631  * The @key is looked up in the "Desktop Entry" group.
4632  *
4633  * Returns: a newly allocated string, or %NULL if the key
4634  *     is not found
4635  *
4636  * Since: 2.36
4637  */
4638 char *
4639 g_desktop_app_info_get_string (GDesktopAppInfo *info,
4640                                const char      *key)
4641 {
4642   g_return_val_if_fail (G_IS_DESKTOP_APP_INFO (info), NULL);
4643
4644   return g_key_file_get_string (info->keyfile,
4645                                 G_KEY_FILE_DESKTOP_GROUP, key, NULL);
4646 }
4647
4648 /**
4649  * g_desktop_app_info_get_locale_string:
4650  * @info: a #GDesktopAppInfo
4651  * @key: the key to look up
4652  *
4653  * Looks up a localized string value in the keyfile backing @info
4654  * translated to the current locale.
4655  *
4656  * The @key is looked up in the "Desktop Entry" group.
4657  *
4658  * Returns: (nullable): a newly allocated string, or %NULL if the key
4659  *     is not found
4660  *
4661  * Since: 2.56
4662  */
4663 char *
4664 g_desktop_app_info_get_locale_string (GDesktopAppInfo *info,
4665                                       const char      *key)
4666 {
4667   g_return_val_if_fail (G_IS_DESKTOP_APP_INFO (info), NULL);
4668   g_return_val_if_fail (key != NULL && *key != '\0', NULL);
4669
4670   return g_key_file_get_locale_string (info->keyfile,
4671                                        G_KEY_FILE_DESKTOP_GROUP,
4672                                        key, NULL, NULL);
4673 }
4674
4675 /**
4676  * g_desktop_app_info_get_boolean:
4677  * @info: a #GDesktopAppInfo
4678  * @key: the key to look up
4679  *
4680  * Looks up a boolean value in the keyfile backing @info.
4681  *
4682  * The @key is looked up in the "Desktop Entry" group.
4683  *
4684  * Returns: the boolean value, or %FALSE if the key
4685  *     is not found
4686  *
4687  * Since: 2.36
4688  */
4689 gboolean
4690 g_desktop_app_info_get_boolean (GDesktopAppInfo *info,
4691                                 const char      *key)
4692 {
4693   g_return_val_if_fail (G_IS_DESKTOP_APP_INFO (info), FALSE);
4694
4695   return g_key_file_get_boolean (info->keyfile,
4696                                  G_KEY_FILE_DESKTOP_GROUP, key, NULL);
4697 }
4698
4699 /**
4700  * g_desktop_app_info_get_string_list:
4701  * @info: a #GDesktopAppInfo
4702  * @key: the key to look up
4703  * @length: (out) (optional): return location for the number of returned strings, or %NULL
4704  *
4705  * Looks up a string list value in the keyfile backing @info.
4706  *
4707  * The @key is looked up in the "Desktop Entry" group.
4708  *
4709  * Returns: (array zero-terminated=1 length=length) (element-type utf8) (transfer full):
4710  *  a %NULL-terminated string array or %NULL if the specified
4711  *  key cannot be found. The array should be freed with g_strfreev().
4712  *
4713  * Since: 2.60.0
4714  */
4715 gchar **
4716 g_desktop_app_info_get_string_list (GDesktopAppInfo *info,
4717                                     const char      *key,
4718                                     gsize           *length)
4719 {
4720   g_return_val_if_fail (G_IS_DESKTOP_APP_INFO (info), NULL);
4721
4722   return g_key_file_get_string_list (info->keyfile,
4723                                      G_KEY_FILE_DESKTOP_GROUP, key, length, NULL);
4724 }
4725
4726 /**
4727  * g_desktop_app_info_has_key:
4728  * @info: a #GDesktopAppInfo
4729  * @key: the key to look up
4730  *
4731  * Returns whether @key exists in the "Desktop Entry" group
4732  * of the keyfile backing @info.
4733  *
4734  * Returns: %TRUE if the @key exists
4735  *
4736  * Since: 2.36
4737  */
4738 gboolean
4739 g_desktop_app_info_has_key (GDesktopAppInfo *info,
4740                             const char      *key)
4741 {
4742   g_return_val_if_fail (G_IS_DESKTOP_APP_INFO (info), FALSE);
4743
4744   return g_key_file_has_key (info->keyfile,
4745                              G_KEY_FILE_DESKTOP_GROUP, key, NULL);
4746 }
4747
4748 /* Desktop actions support {{{2 */
4749
4750 /**
4751  * g_desktop_app_info_list_actions:
4752  * @info: a #GDesktopAppInfo
4753  *
4754  * Returns the list of "additional application actions" supported on the
4755  * desktop file, as per the desktop file specification.
4756  *
4757  * As per the specification, this is the list of actions that are
4758  * explicitly listed in the "Actions" key of the [Desktop Entry] group.
4759  *
4760  * Returns: (array zero-terminated=1) (element-type utf8) (transfer none): a list of strings, always non-%NULL
4761  *
4762  * Since: 2.38
4763  **/
4764 const gchar * const *
4765 g_desktop_app_info_list_actions (GDesktopAppInfo *info)
4766 {
4767   g_return_val_if_fail (G_IS_DESKTOP_APP_INFO (info), NULL);
4768
4769   return (const gchar **) info->actions;
4770 }
4771
4772 static gboolean
4773 app_info_has_action (GDesktopAppInfo *info,
4774                      const gchar     *action_name)
4775 {
4776   gint i;
4777
4778   for (i = 0; info->actions[i]; i++)
4779     if (g_str_equal (info->actions[i], action_name))
4780       return TRUE;
4781
4782   return FALSE;
4783 }
4784
4785 /**
4786  * g_desktop_app_info_get_action_name:
4787  * @info: a #GDesktopAppInfo
4788  * @action_name: the name of the action as from
4789  *   g_desktop_app_info_list_actions()
4790  *
4791  * Gets the user-visible display name of the "additional application
4792  * action" specified by @action_name.
4793  *
4794  * This corresponds to the "Name" key within the keyfile group for the
4795  * action.
4796  *
4797  * Returns: (transfer full): the locale-specific action name
4798  *
4799  * Since: 2.38
4800  */
4801 gchar *
4802 g_desktop_app_info_get_action_name (GDesktopAppInfo *info,
4803                                     const gchar     *action_name)
4804 {
4805   gchar *group_name;
4806   gchar *result;
4807
4808   g_return_val_if_fail (G_IS_DESKTOP_APP_INFO (info), NULL);
4809   g_return_val_if_fail (action_name != NULL, NULL);
4810   g_return_val_if_fail (app_info_has_action (info, action_name), NULL);
4811
4812   group_name = g_strdup_printf ("Desktop Action %s", action_name);
4813   result = g_key_file_get_locale_string (info->keyfile, group_name, "Name", NULL, NULL);
4814   g_free (group_name);
4815
4816   /* The spec says that the Name field must be given.
4817    *
4818    * If it's not, let's follow the behaviour of our get_name()
4819    * implementation above and never return %NULL.
4820    */
4821   if (result == NULL)
4822     result = g_strdup (_("Unnamed"));
4823
4824   return result;
4825 }
4826
4827 /**
4828  * g_desktop_app_info_launch_action:
4829  * @info: a #GDesktopAppInfo
4830  * @action_name: the name of the action as from
4831  *   g_desktop_app_info_list_actions()
4832  * @launch_context: (nullable): a #GAppLaunchContext
4833  *
4834  * Activates the named application action.
4835  *
4836  * You may only call this function on action names that were
4837  * returned from g_desktop_app_info_list_actions().
4838  *
4839  * Note that if the main entry of the desktop file indicates that the
4840  * application supports startup notification, and @launch_context is
4841  * non-%NULL, then startup notification will be used when activating the
4842  * action (and as such, invocation of the action on the receiving side
4843  * must signal the end of startup notification when it is completed).
4844  * This is the expected behaviour of applications declaring additional
4845  * actions, as per the desktop file specification.
4846  *
4847  * As with g_app_info_launch() there is no way to detect failures that
4848  * occur while using this function.
4849  *
4850  * Since: 2.38
4851  */
4852 void
4853 g_desktop_app_info_launch_action (GDesktopAppInfo   *info,
4854                                   const gchar       *action_name,
4855                                   GAppLaunchContext *launch_context)
4856 {
4857   GDBusConnection *session_bus;
4858
4859   g_return_if_fail (G_IS_DESKTOP_APP_INFO (info));
4860   g_return_if_fail (action_name != NULL);
4861   g_return_if_fail (app_info_has_action (info, action_name));
4862
4863   session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
4864
4865   if (session_bus && info->app_id)
4866     {
4867       gchar *object_path;
4868
4869       object_path = object_path_from_appid (info->app_id);
4870       g_dbus_connection_call (session_bus, info->app_id, object_path,
4871                               "org.freedesktop.Application", "ActivateAction",
4872                               g_variant_new ("(sav@a{sv})", action_name, NULL,
4873                                              g_desktop_app_info_make_platform_data (info, NULL, launch_context)),
4874                               NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
4875       g_free (object_path);
4876     }
4877   else
4878     {
4879       gchar *group_name;
4880       gchar *exec_line;
4881
4882       group_name = g_strdup_printf ("Desktop Action %s", action_name);
4883       exec_line = g_key_file_get_string (info->keyfile, group_name, "Exec", NULL);
4884       g_free (group_name);
4885
4886       if (exec_line)
4887         g_desktop_app_info_launch_uris_with_spawn (info, session_bus, exec_line, NULL, launch_context,
4888                                                    _SPAWN_FLAGS_DEFAULT, NULL, NULL, NULL, NULL,
4889                                                    -1, -1, -1, NULL);
4890
4891       g_free (exec_line);
4892     }
4893
4894   if (session_bus != NULL)
4895     {
4896       g_dbus_connection_flush (session_bus, NULL, NULL, NULL);
4897       g_object_unref (session_bus);
4898     }
4899 }
4900 /* Epilogue {{{1 */
4901
4902 /* vim:set foldmethod=marker: */