Bug 559633 – gtk_image_new_from_gicon does not always work for .desktop
[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 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, write to the
18  * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
19  * Boston, MA 02111-1307, USA.
20  *
21  * Author: Alexander Larsson <alexl@redhat.com>
22  */
23
24 #include "config.h"
25
26 #include <errno.h>
27 #include <string.h>
28 #include <unistd.h>
29 #include <sys/wait.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 #include "gfile.h"
38 #include "gioerror.h"
39 #include "gthemedicon.h"
40 #include "gfileicon.h"
41 #include <glib/gstdio.h>
42 #include "glibintl.h"
43 #include "giomodule-priv.h"
44 #include "gappinfo.h"
45
46 #include "gioalias.h"
47
48 /**
49  * SECTION:gdesktopappinfo
50  * @short_description: Application information from desktop files
51  * @include: gio/gdesktopappinfo.h 
52  * 
53  * #GDesktopAppInfo is an implementation of #GAppInfo based on
54  * desktop files.
55  *
56  **/
57
58 #define DEFAULT_APPLICATIONS_GROUP  "Default Applications" 
59 #define ADDED_ASSOCIATIONS_GROUP    "Added Associations" 
60 #define REMOVED_ASSOCIATIONS_GROUP  "Removed Associations" 
61 #define MIME_CACHE_GROUP            "MIME Cache"
62
63 static void     g_desktop_app_info_iface_init         (GAppInfoIface    *iface);
64 static GList *  get_all_desktop_entries_for_mime_type (const char       *base_mime_type,
65                                                        const char      **except);
66 static void     mime_info_cache_reload                (const char       *dir);
67 static gboolean g_desktop_app_info_ensure_saved       (GDesktopAppInfo  *info,
68                                                        GError          **error);
69
70 /**
71  * GDesktopAppInfo:
72  * 
73  * Information about an installed application from a desktop file.
74  */
75 struct _GDesktopAppInfo
76 {
77   GObject parent_instance;
78
79   char *desktop_id;
80   char *filename;
81
82   char *name;
83   /* FIXME: what about GenericName ? */
84   char *comment;
85   char *icon_name;
86   GIcon *icon;
87   char **only_show_in;
88   char **not_show_in;
89   char *try_exec;
90   char *exec;
91   char *binary;
92   char *path;
93
94   guint nodisplay       : 1;
95   guint hidden          : 1;
96   guint terminal        : 1;
97   guint startup_notify  : 1;
98   /* FIXME: what about StartupWMClass ? */
99 };
100
101 G_DEFINE_TYPE_WITH_CODE (GDesktopAppInfo, g_desktop_app_info, G_TYPE_OBJECT,
102                          G_IMPLEMENT_INTERFACE (G_TYPE_APP_INFO,
103                                                 g_desktop_app_info_iface_init))
104
105 static gpointer
106 search_path_init (gpointer data)
107 {
108   char **args = NULL;
109   const char * const *data_dirs;
110   const char *user_data_dir;
111   int i, length, j;
112
113   data_dirs = g_get_system_data_dirs ();
114   length = g_strv_length ((char **) data_dirs);
115   
116   args = g_new (char *, length + 2);
117   
118   j = 0;
119   user_data_dir = g_get_user_data_dir ();
120   args[j++] = g_build_filename (user_data_dir, "applications", NULL);
121   for (i = 0; i < length; i++)
122     args[j++] = g_build_filename (data_dirs[i],
123                                   "applications", NULL);
124   args[j++] = NULL;
125   
126   return args;
127 }
128   
129 static const char * const *
130 get_applications_search_path (void)
131 {
132   static GOnce once_init = G_ONCE_INIT;
133   return g_once (&once_init, search_path_init, NULL);
134 }
135
136 static void
137 g_desktop_app_info_finalize (GObject *object)
138 {
139   GDesktopAppInfo *info;
140
141   info = G_DESKTOP_APP_INFO (object);
142
143   g_free (info->desktop_id);
144   g_free (info->filename);
145   g_free (info->name);
146   g_free (info->comment);
147   g_free (info->icon_name);
148   if (info->icon)
149     g_object_unref (info->icon);
150   g_strfreev (info->only_show_in);
151   g_strfreev (info->not_show_in);
152   g_free (info->try_exec);
153   g_free (info->exec);
154   g_free (info->binary);
155   g_free (info->path);
156   
157   G_OBJECT_CLASS (g_desktop_app_info_parent_class)->finalize (object);
158 }
159
160 static void
161 g_desktop_app_info_class_init (GDesktopAppInfoClass *klass)
162 {
163   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
164   
165   gobject_class->finalize = g_desktop_app_info_finalize;
166 }
167
168 static void
169 g_desktop_app_info_init (GDesktopAppInfo *local)
170 {
171 }
172
173 static char *
174 binary_from_exec (const char *exec)
175 {
176   const char *p, *start;
177   
178   p = exec;
179   while (*p == ' ')
180     p++;
181   start = p;
182   while (*p != ' ' && *p != 0)
183     p++;
184   
185   return g_strndup (start, p - start);
186   
187 }
188
189 /**
190  * g_desktop_app_info_new_from_keyfile:
191  * @key_file: an opened #GKeyFile
192  * 
193  * Creates a new #GDesktopAppInfo.
194  *
195  * Returns: a new #GDesktopAppInfo or %NULL on error.
196  *
197  * Since: 2.18
198  **/
199 GDesktopAppInfo *
200 g_desktop_app_info_new_from_keyfile (GKeyFile *key_file)
201 {
202   GDesktopAppInfo *info;
203   char *start_group;
204   char *type;
205   char *try_exec;
206   
207   start_group = g_key_file_get_start_group (key_file);
208   if (start_group == NULL || strcmp (start_group, G_KEY_FILE_DESKTOP_GROUP) != 0)
209     {
210       g_free (start_group);
211       return NULL;
212     }
213   g_free (start_group);
214
215   type = g_key_file_get_string (key_file,
216                                 G_KEY_FILE_DESKTOP_GROUP,
217                                 G_KEY_FILE_DESKTOP_KEY_TYPE,
218                                 NULL);
219   if (type == NULL || strcmp (type, G_KEY_FILE_DESKTOP_TYPE_APPLICATION) != 0)
220     {
221       g_free (type);
222       return NULL;
223     }
224   g_free (type);
225
226   try_exec = g_key_file_get_string (key_file,
227                                     G_KEY_FILE_DESKTOP_GROUP,
228                                     G_KEY_FILE_DESKTOP_KEY_TRY_EXEC,
229                                     NULL);
230   if (try_exec && try_exec[0] != '\0')
231     {
232       char *t;
233       t = g_find_program_in_path (try_exec);
234       if (t == NULL)
235         {
236           g_free (try_exec);
237           return NULL;
238         }
239       g_free (t);
240     }
241
242   info = g_object_new (G_TYPE_DESKTOP_APP_INFO, NULL);
243   info->filename = NULL;
244
245   info->name = g_key_file_get_locale_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NAME, NULL, NULL);
246   info->comment = g_key_file_get_locale_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_COMMENT, NULL, NULL);
247   info->nodisplay = g_key_file_get_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY, NULL) != FALSE;
248   info->icon_name =  g_key_file_get_locale_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ICON, NULL, NULL);
249   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);
250   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);
251   info->try_exec = try_exec;
252   info->exec = g_key_file_get_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_EXEC, NULL);
253   info->path = g_key_file_get_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_PATH, NULL);
254   info->terminal = g_key_file_get_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_TERMINAL, NULL) != FALSE;
255   info->startup_notify = g_key_file_get_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_STARTUP_NOTIFY, NULL) != FALSE;
256   info->hidden = g_key_file_get_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_HIDDEN, NULL) != FALSE;
257   
258   info->icon = NULL;
259   if (info->icon_name)
260     {
261       if (g_path_is_absolute (info->icon_name))
262         {
263           GFile *file;
264           
265           file = g_file_new_for_path (info->icon_name);
266           info->icon = g_file_icon_new (file);
267           g_object_unref (file);
268         }
269       else
270         {
271           char *p;
272
273           /* Work around a common mistake in desktop files */    
274           if ((p = strrchr (info->icon_name, '.')) != NULL &&
275               (strcmp (p, ".png") == 0 ||
276                strcmp (p, ".xpm") == 0 ||
277                strcmp (p, ".svg") == 0)) 
278             *p = 0;
279
280           info->icon = g_themed_icon_new (info->icon_name);
281         }
282     }
283   
284   if (info->exec)
285     info->binary = binary_from_exec (info->exec);
286   
287   if (info->path && info->path[0] == '\0')
288     {
289       g_free (info->path);
290       info->path = NULL;
291     }
292
293   return info;
294 }
295
296 /**
297  * g_desktop_app_info_new_from_filename:
298  * @filename: the path of a desktop file, in the GLib filename encoding
299  * 
300  * Creates a new #GDesktopAppInfo.
301  *
302  * Returns: a new #GDesktopAppInfo or %NULL on error.
303  **/
304 GDesktopAppInfo *
305 g_desktop_app_info_new_from_filename (const char *filename)
306 {
307   GKeyFile *key_file;
308   GDesktopAppInfo *info = NULL;
309
310   key_file = g_key_file_new ();
311   
312   if (g_key_file_load_from_file (key_file,
313                                  filename,
314                                  G_KEY_FILE_NONE,
315                                  NULL))
316     {
317       info = g_desktop_app_info_new_from_keyfile (key_file);
318       if (info)
319         info->filename = g_strdup (filename);
320     }  
321
322   g_key_file_free (key_file);
323
324   return info;
325 }
326
327 /**
328  * g_desktop_app_info_new:
329  * @desktop_id: the desktop file id
330  * 
331  * Creates a new #GDesktopAppInfo.
332  * 
333  * Returns: a new #GDesktopAppInfo, or %NULL if no desktop file with that id
334  **/
335 GDesktopAppInfo *
336 g_desktop_app_info_new (const char *desktop_id)
337 {
338   GDesktopAppInfo *appinfo;
339   const char * const *dirs;
340   char *basename;
341   int i;
342
343   dirs = get_applications_search_path ();
344
345   basename = g_strdup (desktop_id);
346   
347   for (i = 0; dirs[i] != NULL; i++)
348     {
349       char *filename;
350       char *p;
351
352       filename = g_build_filename (dirs[i], desktop_id, NULL);
353       appinfo = g_desktop_app_info_new_from_filename (filename);
354       g_free (filename);
355       if (appinfo != NULL)
356         goto found;
357
358       p = basename;
359       while ((p = strchr (p, '-')) != NULL)
360         {
361           *p = '/';
362           
363           filename = g_build_filename (dirs[i], basename, NULL);
364           appinfo = g_desktop_app_info_new_from_filename (filename);
365           g_free (filename);
366           if (appinfo != NULL)
367             goto found;
368           *p = '-';
369           p++;
370         }
371     }
372   
373   g_free (basename);
374   return NULL;
375
376  found:
377   g_free (basename);
378   
379   appinfo->desktop_id = g_strdup (desktop_id);
380
381   if (g_desktop_app_info_get_is_hidden (appinfo))
382     {
383       g_object_unref (appinfo);
384       appinfo = NULL;
385     }
386   
387   return appinfo;
388 }
389
390 static GAppInfo *
391 g_desktop_app_info_dup (GAppInfo *appinfo)
392 {
393   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
394   GDesktopAppInfo *new_info;
395   
396   new_info = g_object_new (G_TYPE_DESKTOP_APP_INFO, NULL);
397
398   new_info->filename = g_strdup (info->filename);
399   new_info->desktop_id = g_strdup (info->desktop_id);
400   
401   new_info->name = g_strdup (info->name);
402   new_info->comment = g_strdup (info->comment);
403   new_info->nodisplay = info->nodisplay;
404   new_info->icon_name = g_strdup (info->icon_name);
405   new_info->icon = g_object_ref (info->icon);
406   new_info->only_show_in = g_strdupv (info->only_show_in);
407   new_info->not_show_in = g_strdupv (info->not_show_in);
408   new_info->try_exec = g_strdup (info->try_exec);
409   new_info->exec = g_strdup (info->exec);
410   new_info->binary = g_strdup (info->binary);
411   new_info->path = g_strdup (info->path);
412   new_info->hidden = info->hidden;
413   new_info->terminal = info->terminal;
414   new_info->startup_notify = info->startup_notify;
415   
416   return G_APP_INFO (new_info);
417 }
418
419 static gboolean
420 g_desktop_app_info_equal (GAppInfo *appinfo1,
421                           GAppInfo *appinfo2)
422 {
423   GDesktopAppInfo *info1 = G_DESKTOP_APP_INFO (appinfo1);
424   GDesktopAppInfo *info2 = G_DESKTOP_APP_INFO (appinfo2);
425
426   if (info1->desktop_id == NULL ||
427       info2->desktop_id == NULL)
428     return info1 == info2;
429
430   return strcmp (info1->desktop_id, info2->desktop_id) == 0;
431 }
432
433 static const char *
434 g_desktop_app_info_get_id (GAppInfo *appinfo)
435 {
436   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
437
438   return info->desktop_id;
439 }
440
441 static const char *
442 g_desktop_app_info_get_name (GAppInfo *appinfo)
443 {
444   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
445
446   if (info->name == NULL)
447     return _("Unnamed");
448   return info->name;
449 }
450
451 /**
452  * g_desktop_app_info_get_is_hidden:
453  * @info: a #GDesktopAppInfo.
454  *
455  * A desktop file is hidden if the Hidden key in it is
456  * set to True.
457  *
458  * Returns: %TRUE if hidden, %FALSE otherwise. 
459  **/
460 gboolean
461 g_desktop_app_info_get_is_hidden (GDesktopAppInfo *info)
462 {
463   return info->hidden;
464 }
465
466 static const char *
467 g_desktop_app_info_get_description (GAppInfo *appinfo)
468 {
469   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
470   
471   return info->comment;
472 }
473
474 static const char *
475 g_desktop_app_info_get_executable (GAppInfo *appinfo)
476 {
477   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
478   
479   return info->binary;
480 }
481
482 static GIcon *
483 g_desktop_app_info_get_icon (GAppInfo *appinfo)
484 {
485   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
486
487   return info->icon;
488 }
489
490 static char *
491 expand_macro_single (char macro, char *uri)
492 {
493   GFile *file;
494   char *result = NULL;
495   char *path, *name;
496
497   file = g_file_new_for_uri (uri);
498   path = g_file_get_path (file);
499   g_object_unref (file);
500   
501   switch (macro)
502     {
503     case 'u':
504     case 'U':   
505       result = g_shell_quote (uri);
506       break;
507     case 'f':
508     case 'F':
509       if (path)
510         result = g_shell_quote (path);
511       break;
512     case 'd':
513     case 'D':
514       if (path)
515         {
516           name = g_path_get_dirname (path);
517           result = g_shell_quote (name);
518           g_free (name);
519         }
520       break;
521     case 'n':
522     case 'N':
523       if (path)
524         {
525           name = g_path_get_basename (path);
526           result = g_shell_quote (name);
527           g_free (name);
528         }
529       break;
530     }
531
532   g_free (path);
533   
534   return result;
535 }
536
537 static void
538 expand_macro (char              macro, 
539               GString          *exec, 
540               GDesktopAppInfo  *info, 
541               GList           **uri_list)
542 {
543   GList *uris = *uri_list;
544   char *expanded;
545   gboolean force_file_uri;
546   char force_file_uri_macro;
547
548   g_return_if_fail (exec != NULL);
549
550   /* On %u and %U, pass POSIX file path pointing to the URI via
551    * the FUSE mount in ~/.gvfs. Note that if the FUSE daemon isn't
552    * running or the URI doesn't have a POSIX file path via FUSE
553    * we'll just pass the URI.
554    */
555   switch (macro)
556     {
557     case 'u':
558       force_file_uri_macro = 'f';
559       force_file_uri = TRUE;
560       break;
561     case 'U':
562       force_file_uri_macro = 'F';
563       force_file_uri = TRUE;
564       break;
565     default:
566       force_file_uri_macro = macro;
567       force_file_uri = FALSE;
568       break;
569     }
570
571   switch (macro)
572     {
573     case 'u':
574     case 'f':
575     case 'd':
576     case 'n':
577       if (uris)
578         {
579           if (!force_file_uri)
580             {
581               expanded = expand_macro_single (macro, uris->data);
582             }
583           else
584             {
585               expanded = expand_macro_single (force_file_uri_macro, uris->data);
586               if (expanded == NULL)
587                 expanded = expand_macro_single (macro, uris->data);
588             }
589
590           if (expanded)
591             {
592               g_string_append (exec, expanded);
593               g_free (expanded);
594             }
595           uris = uris->next;
596         }
597
598       break;
599
600     case 'U':   
601     case 'F':
602     case 'D':
603     case 'N':
604       while (uris)
605         {
606           if (!force_file_uri)
607             {
608               expanded = expand_macro_single (macro, uris->data);
609             }
610           else
611             {
612               expanded = expand_macro_single (force_file_uri_macro, uris->data);
613               if (expanded == NULL)
614                 expanded = expand_macro_single (macro, uris->data);
615             }
616
617           if (expanded)
618             {
619               g_string_append (exec, expanded);
620               g_free (expanded);
621             }
622           
623           uris = uris->next;
624           
625           if (uris != NULL && expanded)
626             g_string_append_c (exec, ' ');
627         }
628
629       break;
630
631     case 'i':
632       if (info->icon_name)
633         {
634           g_string_append (exec, "--icon ");
635           g_string_append (exec, info->icon_name);
636         }
637       break;
638
639     case 'c':
640       if (info->name) 
641         g_string_append (exec, info->name);
642       break;
643
644     case 'k':
645       if (info->filename) 
646         g_string_append (exec, info->filename);
647       break;
648
649     case 'm': /* deprecated */
650       break;
651
652     case '%':
653       g_string_append_c (exec, '%');
654       break;
655     }
656   
657   *uri_list = uris;
658 }
659
660 static gboolean
661 expand_application_parameters (GDesktopAppInfo   *info,
662                                GList            **uris,
663                                int               *argc,
664                                char            ***argv,
665                                GError           **error)
666 {
667   GList *uri_list = *uris;
668   const char *p = info->exec;
669   GString *expanded_exec = g_string_new (NULL);
670   gboolean res;
671   
672   if (info->exec == NULL)
673     {
674       g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
675                            _("Desktop file didn't specify Exec field"));
676       return FALSE;
677     }
678   
679   while (*p)
680     {
681       if (p[0] == '%' && p[1] != '\0')
682         {
683           expand_macro (p[1], expanded_exec, info, uris);
684           p++;
685         }
686       else
687         g_string_append_c (expanded_exec, *p);
688       
689       p++;
690     }
691   
692   /* No file substitutions */
693   if (uri_list == *uris && uri_list != NULL)
694     {
695       /* If there is no macro default to %f. This is also what KDE does */
696       g_string_append_c (expanded_exec, ' ');
697       expand_macro ('f', expanded_exec, info, uris);
698     }
699   
700   res = g_shell_parse_argv (expanded_exec->str, argc, argv, error);
701   g_string_free (expanded_exec, TRUE);
702   return res;
703 }
704
705 static gboolean
706 prepend_terminal_to_vector (int    *argc,
707                             char ***argv)
708 {
709 #ifndef G_OS_WIN32
710   char **real_argv;
711   int real_argc;
712   int i, j;
713   char **term_argv = NULL;
714   int term_argc = 0;
715   char *check;
716   char **the_argv;
717   
718   g_return_val_if_fail (argc != NULL, FALSE);
719   g_return_val_if_fail (argv != NULL, FALSE);
720         
721   /* sanity */
722   if(*argv == NULL)
723     *argc = 0;
724   
725   the_argv = *argv;
726
727   /* compute size if not given */
728   if (*argc < 0)
729     {
730       for (i = 0; the_argv[i] != NULL; i++)
731         ;
732       *argc = i;
733     }
734   
735   term_argc = 2;
736   term_argv = g_new0 (char *, 3);
737
738   check = g_find_program_in_path ("gnome-terminal");
739   if (check != NULL)
740     {
741       term_argv[0] = check;
742       /* Note that gnome-terminal takes -x and
743        * as -e in gnome-terminal is broken we use that. */
744       term_argv[1] = g_strdup ("-x");
745     }
746   else
747     {
748       if (check == NULL)
749         check = g_find_program_in_path ("nxterm");
750       if (check == NULL)
751         check = g_find_program_in_path ("color-xterm");
752       if (check == NULL)
753         check = g_find_program_in_path ("rxvt");
754       if (check == NULL)
755         check = g_find_program_in_path ("xterm");
756       if (check == NULL)
757         check = g_find_program_in_path ("dtterm");
758       if (check == NULL)
759         {
760           check = g_strdup ("xterm");
761           g_warning ("couldn't find a terminal, falling back to xterm");
762         }
763       term_argv[0] = check;
764       term_argv[1] = g_strdup ("-e");
765     }
766
767   real_argc = term_argc + *argc;
768   real_argv = g_new (char *, real_argc + 1);
769   
770   for (i = 0; i < term_argc; i++)
771     real_argv[i] = term_argv[i];
772   
773   for (j = 0; j < *argc; j++, i++)
774     real_argv[i] = (char *)the_argv[j];
775   
776   real_argv[i] = NULL;
777   
778   g_free (*argv);
779   *argv = real_argv;
780   *argc = real_argc;
781   
782   /* we use g_free here as we sucked all the inner strings
783    * out from it into real_argv */
784   g_free (term_argv);
785   return TRUE;
786 #else
787   return FALSE;
788 #endif /* G_OS_WIN32 */
789 }
790
791 /* '=' is the new '\0'.
792  * DO NOT CALL unless at least one string ends with '='
793  */
794 static gboolean
795 is_env (const char *a,
796         const char *b)
797 {
798   while (*a == *b)
799   {
800     if (*a == 0 || *b == 0)
801       return FALSE;
802     
803     if (*a == '=')
804       return TRUE;
805
806     a++;
807     b++;
808   }
809
810   return FALSE;
811 }
812
813 /* free with g_strfreev */
814 static char **
815 replace_env_var (char       **old_environ,
816                  const char  *env_var,
817                  const char  *new_value)
818 {
819   int length, new_length;
820   int index, new_index;
821   char **new_environ;
822   int i, new_i;
823
824   /* do two things at once:
825    *  - discover the length of the environment ('length')
826    *  - find the location (if any) of the env var ('index')
827    */
828   index = -1;
829   for (length = 0; old_environ[length]; length++)
830     {
831       /* if we already have it in our environment, replace */
832       if (is_env (old_environ[length], env_var))
833         index = length;
834     }
835
836   
837   /* no current env var, no desired env value.
838    * this is easy :)
839    */
840   if (new_value == NULL && index == -1)
841     return old_environ;
842
843   /* in all cases now, we will be using a modified environment.
844    * determine its length and allocated it.
845    * 
846    * after this block:
847    *   new_index   = location to insert, if any
848    *   new_length  = length of the new array
849    *   new_environ = the pointer array for the new environment
850    */
851   
852   if (new_value == NULL && index >= 0)
853     {
854       /* in this case, we will be removing an entry */
855       new_length = length - 1;
856       new_index = -1;
857     }
858   else if (new_value != NULL && index < 0)
859     {
860       /* in this case, we will be adding an entry to the end */
861       new_length = length + 1;
862       new_index = length;
863     }
864   else
865     /* in this case, we will be replacing the existing entry */
866     {
867       new_length = length;
868       new_index = index;
869     }
870
871   new_environ = g_malloc (sizeof (char *) * (new_length + 1));
872   new_environ[new_length] = NULL;
873
874   /* now we do the copying.
875    * for each entry in the new environment, we decide what to do
876    */
877   
878   i = 0;
879   for (new_i = 0; new_i < new_length; new_i++)
880     {
881       if (new_i == new_index)
882         {
883           /* insert our new item */
884           new_environ[new_i] = g_strconcat (env_var,
885                                             "=",
886                                             new_value,
887                                             NULL);
888           
889           /* if we had an old entry, skip it now */
890           if (index >= 0)
891             i++;
892         }
893       else
894         {
895           /* if this is the old DESKTOP_STARTUP_ID, skip it */
896           if (i == index)
897             i++;
898           
899           /* copy an old item */
900           new_environ[new_i] = g_strdup (old_environ[i]);
901           i++;
902         }
903     }
904
905   g_strfreev (old_environ);
906   
907   return new_environ;
908 }
909
910 static GList *
911 uri_list_segment_to_files (GList *start,
912                            GList *end)
913 {
914   GList *res;
915   GFile *file;
916
917   res = NULL;
918   while (start != NULL && start != end)
919     {
920       file = g_file_new_for_uri ((char *)start->data);
921       res = g_list_prepend (res, file);
922       start = start->next;
923     }
924
925   return g_list_reverse (res);
926 }
927
928 #ifdef HAVE__NSGETENVIRON
929 #define environ (*_NSGetEnviron())
930 #elif !defined(G_OS_WIN32)
931
932 /* According to the Single Unix Specification, environ is not in 
933  *  * any system header, although unistd.h often declares it.
934  *   */
935 extern char **environ;
936 #endif
937
938 static gboolean
939 g_desktop_app_info_launch_uris (GAppInfo           *appinfo,
940                                 GList              *uris,
941                                 GAppLaunchContext  *launch_context,
942                                 GError            **error)
943 {
944   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
945   gboolean completed = FALSE;
946   GList *old_uris;
947   GList *launched_files;
948   char **envp;
949   char **argv;
950   int argc;
951   char *display;
952   char *sn_id;
953
954   g_return_val_if_fail (appinfo != NULL, FALSE);
955
956   argv = NULL;
957   envp = NULL;
958       
959   do 
960     {
961       old_uris = uris;
962       if (!expand_application_parameters (info, &uris,
963                                           &argc, &argv, error))
964         goto out;
965       
966       if (info->terminal && !prepend_terminal_to_vector (&argc, &argv))
967         {
968           g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
969                                _("Unable to find terminal required for application"));
970           goto out;
971         }
972
973       sn_id = NULL;
974       if (launch_context)
975         {
976           launched_files = uri_list_segment_to_files (old_uris, uris);
977           
978           display = g_app_launch_context_get_display (launch_context,
979                                                       appinfo,
980                                                       launched_files);
981
982           sn_id = NULL;
983           if (info->startup_notify)
984             sn_id = g_app_launch_context_get_startup_notify_id (launch_context,
985                                                                 appinfo,
986                                                                 launched_files);
987           
988           if (display || sn_id)
989             {
990 #ifdef G_OS_WIN32
991               /* FIXME */
992               envp = g_new0 (char *, 1);
993 #else
994               envp = g_strdupv (environ);
995 #endif
996               
997               if (display)
998                 envp = replace_env_var (envp,
999                                         "DISPLAY",
1000                                         display);
1001               
1002               if (sn_id)
1003                 envp = replace_env_var (envp,
1004                                         "DESKTOP_STARTUP_ID",
1005                                         sn_id);
1006             }
1007
1008           g_free (display);
1009           
1010           g_list_foreach (launched_files, (GFunc)g_object_unref, NULL);
1011           g_list_free (launched_files);
1012         }
1013       
1014       if (!g_spawn_async (info->path,  /* working directory */
1015                           argv,
1016                           envp,
1017                           G_SPAWN_SEARCH_PATH /* flags */,
1018                           NULL /* child_setup */,
1019                           NULL /* data */,
1020                           NULL /* child_pid */,
1021                           error))
1022         {
1023           if (sn_id)
1024             {
1025               g_app_launch_context_launch_failed (launch_context, sn_id);
1026               g_free (sn_id);
1027             }
1028           goto out;
1029         }
1030
1031       
1032       g_free (sn_id);
1033       
1034       g_strfreev (envp);
1035       g_strfreev (argv);
1036       envp = NULL;
1037       argv = NULL;
1038     }
1039   while (uris != NULL);
1040
1041   completed = TRUE;
1042
1043  out:
1044   g_strfreev (argv);
1045   g_strfreev (envp);
1046
1047   return completed;
1048 }
1049
1050 static gboolean
1051 g_desktop_app_info_supports_uris (GAppInfo *appinfo)
1052 {
1053   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
1054  
1055   return info->exec && 
1056     ((strstr (info->exec, "%u") != NULL) ||
1057      (strstr (info->exec, "%U") != NULL));
1058 }
1059
1060 static gboolean
1061 g_desktop_app_info_supports_files (GAppInfo *appinfo)
1062 {
1063   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
1064  
1065   return info->exec && 
1066     ((strstr (info->exec, "%f") != NULL) ||
1067      (strstr (info->exec, "%F") != NULL));
1068 }
1069
1070 static gboolean
1071 g_desktop_app_info_launch (GAppInfo           *appinfo,
1072                            GList              *files,
1073                            GAppLaunchContext  *launch_context,
1074                            GError            **error)
1075 {
1076   GList *uris;
1077   char *uri;
1078   gboolean res;
1079
1080   uris = NULL;
1081   while (files)
1082     {
1083       uri = g_file_get_uri (files->data);
1084       uris = g_list_prepend (uris, uri);
1085       files = files->next;
1086     }
1087   
1088   uris = g_list_reverse (uris);
1089   
1090   res = g_desktop_app_info_launch_uris (appinfo, uris, launch_context, error);
1091   
1092   g_list_foreach  (uris, (GFunc)g_free, NULL);
1093   g_list_free (uris);
1094   
1095   return res;
1096 }
1097
1098 G_LOCK_DEFINE_STATIC (g_desktop_env);
1099 static gchar *g_desktop_env = NULL;
1100
1101 /**
1102  * g_desktop_app_info_set_desktop_env:
1103  * @desktop_env: a string specifying what desktop this is
1104  *
1105  * Sets the name of the desktop that the application is running in.
1106  * This is used by g_app_info_should_show() to evaluate the
1107  * <literal>OnlyShowIn</literal> and <literal>NotShowIn</literal>
1108  * desktop entry fields.
1109  *
1110  * The <ulink url="http://standards.freedesktop.org/menu-spec/latest/">Desktop 
1111  * Menu specification</ulink> recognizes the following:
1112  * <simplelist>
1113  *   <member>GNOME</member>
1114  *   <member>KDE</member>
1115  *   <member>ROX</member>
1116  *   <member>XFCE</member>
1117  *   <member>Old</member> 
1118  * </simplelist>
1119  *
1120  * Should be called only once; subsequent calls are ignored.
1121  */
1122 void
1123 g_desktop_app_info_set_desktop_env (const gchar *desktop_env)
1124 {
1125   G_LOCK (g_desktop_env);
1126   if (!g_desktop_env)
1127     g_desktop_env = g_strdup (desktop_env);
1128   G_UNLOCK (g_desktop_env);
1129 }
1130
1131 static gboolean
1132 g_desktop_app_info_should_show (GAppInfo *appinfo)
1133 {
1134   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
1135   gboolean found;
1136   const gchar *desktop_env;
1137   int i;
1138
1139   if (info->nodisplay)
1140     return FALSE;
1141
1142   G_LOCK (g_desktop_env);
1143   desktop_env = g_desktop_env;
1144   G_UNLOCK (g_desktop_env);
1145
1146   if (info->only_show_in)
1147     {
1148       if (desktop_env == NULL)
1149         return FALSE;
1150       
1151       found = FALSE;
1152       for (i = 0; info->only_show_in[i] != NULL; i++)
1153         {
1154           if (strcmp (info->only_show_in[i], desktop_env) == 0)
1155             {
1156               found = TRUE;
1157               break;
1158             }
1159         }
1160       if (!found)
1161         return FALSE;
1162     }
1163
1164   if (info->not_show_in && desktop_env)
1165     {
1166       for (i = 0; info->not_show_in[i] != NULL; i++)
1167         {
1168           if (strcmp (info->not_show_in[i], desktop_env) == 0)
1169             return FALSE;
1170         }
1171     }
1172   
1173   return TRUE;
1174 }
1175
1176 typedef enum {
1177   APP_DIR,
1178   MIMETYPE_DIR
1179 } DirType;
1180
1181 static char *
1182 ensure_dir (DirType   type,
1183             GError  **error)
1184 {
1185   char *path, *display_name;
1186   int errsv;
1187
1188   if (type == APP_DIR)
1189     path = g_build_filename (g_get_user_data_dir (), "applications", NULL);
1190   else
1191     path = g_build_filename (g_get_user_data_dir (), "mime", "packages", NULL);
1192
1193   errno = 0;
1194   if (g_mkdir_with_parents (path, 0700) == 0)
1195     return path;
1196
1197   errsv = errno;
1198   display_name = g_filename_display_name (path);
1199   if (type == APP_DIR)
1200     g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
1201                  _("Can't create user application configuration folder %s: %s"),
1202                  display_name, g_strerror (errsv));
1203   else
1204     g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
1205                  _("Can't create user MIME configuration folder %s: %s"),
1206                  display_name, g_strerror (errsv));
1207
1208   g_free (display_name);
1209   g_free (path);
1210
1211   return NULL;
1212 }
1213
1214 static gboolean
1215 update_mimeapps_list (const char  *desktop_id, 
1216                       const char  *content_type, 
1217                       gboolean     add_as_default,
1218                       gboolean     add_non_default,
1219                       gboolean     remove, 
1220                       GError     **error)
1221 {
1222   char *dirname, *filename;
1223   GKeyFile *key_file;
1224   gboolean load_succeeded, res;
1225   char **old_list, **list;
1226   GList *system_list, *l;
1227   gsize length, data_size;
1228   char *data;
1229   int i, j, k;
1230   char **content_types;
1231
1232   /* Don't add both at start and end */
1233   g_assert (!(add_as_default && add_non_default));
1234   
1235   dirname = ensure_dir (APP_DIR, error);
1236   if (!dirname)
1237     return FALSE;
1238
1239   filename = g_build_filename (dirname, "mimeapps.list", NULL);
1240   g_free (dirname);
1241
1242   key_file = g_key_file_new ();
1243   load_succeeded = g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, NULL);
1244   if (!load_succeeded || !g_key_file_has_group (key_file, ADDED_ASSOCIATIONS_GROUP))
1245     {
1246       g_key_file_free (key_file);
1247       key_file = g_key_file_new ();
1248     }
1249
1250   if (content_type)
1251     {
1252       content_types = g_new (char *, 2);
1253       content_types[0] = g_strdup (content_type);
1254       content_types[1] = NULL;
1255     }
1256   else
1257     {
1258       content_types = g_key_file_get_keys (key_file, ADDED_ASSOCIATIONS_GROUP, NULL, NULL);
1259     }
1260
1261   for (k = 0; content_types && content_types[k]; k++)
1262     { 
1263       /* Add to the right place in the list */
1264   
1265       length = 0;
1266       old_list = g_key_file_get_string_list (key_file, ADDED_ASSOCIATIONS_GROUP,
1267                                              content_types[k], &length, NULL);
1268
1269       list = g_new (char *, 1 + length + 1);
1270
1271       i = 0;
1272       if (add_as_default)
1273         list[i++] = g_strdup (desktop_id);
1274       if (old_list)
1275         {
1276           for (j = 0; old_list[j] != NULL; j++)
1277             {
1278               if (g_strcmp0 (old_list[j], desktop_id) != 0)
1279                 list[i++] = g_strdup (old_list[j]);
1280               else if (add_non_default)
1281                 {
1282                   /* If adding as non-default, and its already in,
1283                      don't change order of desktop ids */
1284                   add_non_default = FALSE;
1285                   list[i++] = g_strdup (old_list[j]);
1286                 }
1287             }
1288         }
1289       
1290       if (add_non_default)
1291         {
1292           /* We're adding as non-default, and it wasn't already in the list,
1293              so we add at the end. But to avoid listing the app before the
1294              current system default (thus changing the default) we have to
1295              add the current list of (not yet listed) apps before it. */
1296
1297           list[i] = NULL; /* Terminate current list so we can use it */
1298           system_list =  get_all_desktop_entries_for_mime_type (content_type, list);
1299           
1300           list = g_renew (char *, list, 1 + length + g_list_length (system_list) + 1);
1301           
1302           for (l = system_list; l != NULL; l = l->next)
1303             {
1304               list[i++] = l->data; /* no strdup, taking ownership */
1305               if (g_strcmp0 (l->data, desktop_id) == 0)
1306                 add_non_default = FALSE;
1307             }
1308           g_list_free (system_list);
1309                   
1310           if (add_non_default)
1311             list[i++] = g_strdup (desktop_id);
1312         }
1313       
1314       list[i] = NULL;
1315   
1316       g_strfreev (old_list);
1317
1318       if (list[0] == NULL || desktop_id == NULL)
1319         g_key_file_remove_key (key_file,
1320                                ADDED_ASSOCIATIONS_GROUP,
1321                                content_types[k],
1322                                NULL);
1323       else
1324         g_key_file_set_string_list (key_file,
1325                                     ADDED_ASSOCIATIONS_GROUP,
1326                                     content_types[k],
1327                                     (const char * const *)list, i);
1328    
1329       g_strfreev (list);
1330     }
1331   
1332   if (content_type)
1333     {
1334       /* reuse the list from above */
1335     }
1336   else
1337     {
1338       g_strfreev (content_types);
1339       content_types = g_key_file_get_keys (key_file, REMOVED_ASSOCIATIONS_GROUP, NULL, NULL);
1340     }
1341
1342   for (k = 0; content_types && content_types[k]; k++) 
1343     {
1344       /* Remove from removed associations group (unless remove) */
1345   
1346       length = 0;
1347       old_list = g_key_file_get_string_list (key_file, REMOVED_ASSOCIATIONS_GROUP,
1348                                              content_types[k], &length, NULL);
1349
1350       list = g_new (char *, 1 + length + 1);
1351
1352       i = 0;
1353       if (remove)
1354         list[i++] = g_strdup (desktop_id);
1355       if (old_list)
1356         {
1357           for (j = 0; old_list[j] != NULL; j++)
1358             {
1359               if (g_strcmp0 (old_list[j], desktop_id) != 0)
1360                 list[i++] = g_strdup (old_list[j]);
1361             }
1362         }
1363       list[i] = NULL;
1364   
1365       g_strfreev (old_list);
1366
1367       if (list[0] == NULL || desktop_id == NULL)
1368         g_key_file_remove_key (key_file,
1369                                REMOVED_ASSOCIATIONS_GROUP,
1370                                content_types[k],
1371                                NULL);
1372       else
1373         g_key_file_set_string_list (key_file,
1374                                     REMOVED_ASSOCIATIONS_GROUP,
1375                                     content_types[k],
1376                                     (const char * const *)list, i);
1377
1378       g_strfreev (list);
1379     }
1380   
1381   g_strfreev (content_types);  
1382
1383   data = g_key_file_to_data (key_file, &data_size, error);
1384   g_key_file_free (key_file);
1385   
1386   res = g_file_set_contents (filename, data, data_size, error);
1387
1388   mime_info_cache_reload (NULL);
1389                           
1390   g_free (filename);
1391   g_free (data);
1392   
1393   return res;
1394 }
1395
1396 static gboolean
1397 g_desktop_app_info_set_as_default_for_type (GAppInfo    *appinfo,
1398                                             const char  *content_type,
1399                                             GError     **error)
1400 {
1401   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
1402
1403   if (!g_desktop_app_info_ensure_saved (info, error))
1404     return FALSE;  
1405   
1406   return update_mimeapps_list (info->desktop_id, content_type, TRUE, FALSE, FALSE, error);
1407 }
1408
1409 static void
1410 update_program_done (GPid     pid,
1411                      gint     status,
1412                      gpointer data)
1413 {
1414   /* Did the application exit correctly */
1415   if (WIFEXITED (status) &&
1416       WEXITSTATUS (status) == 0)
1417     {
1418       /* Here we could clean out any caches in use */
1419     }
1420 }
1421
1422 static void
1423 run_update_command (char *command,
1424                     char *subdir)
1425 {
1426         char *argv[3] = {
1427                 NULL,
1428                 NULL,
1429                 NULL,
1430         };
1431         GPid pid = 0;
1432         GError *error = NULL;
1433
1434         argv[0] = command;
1435         argv[1] = g_build_filename (g_get_user_data_dir (), subdir, NULL);
1436
1437         if (g_spawn_async ("/", argv,
1438                            NULL,       /* envp */
1439                            G_SPAWN_SEARCH_PATH |
1440                            G_SPAWN_STDOUT_TO_DEV_NULL |
1441                            G_SPAWN_STDERR_TO_DEV_NULL |
1442                            G_SPAWN_DO_NOT_REAP_CHILD,
1443                            NULL, NULL, /* No setup function */
1444                            &pid,
1445                            &error)) 
1446           g_child_watch_add (pid, update_program_done, NULL);
1447         else
1448           {
1449             /* If we get an error at this point, it's quite likely the user doesn't
1450              * have an installed copy of either 'update-mime-database' or
1451              * 'update-desktop-database'.  I don't think we want to popup an error
1452              * dialog at this point, so we just do a g_warning to give the user a
1453              * chance of debugging it.
1454              */
1455             g_warning ("%s", error->message);
1456           }
1457         
1458         g_free (argv[1]);
1459 }
1460
1461 static gboolean
1462 g_desktop_app_info_set_as_default_for_extension (GAppInfo    *appinfo,
1463                                                  const char  *extension,
1464                                                  GError     **error)
1465 {
1466   char *filename, *basename, *mimetype;
1467   char *dirname;
1468   gboolean res;
1469
1470   if (!g_desktop_app_info_ensure_saved (G_DESKTOP_APP_INFO (appinfo), error))
1471     return FALSE;  
1472   
1473   dirname = ensure_dir (MIMETYPE_DIR, error);
1474   if (!dirname)
1475     return FALSE;
1476   
1477   basename = g_strdup_printf ("user-extension-%s.xml", extension);
1478   filename = g_build_filename (dirname, basename, NULL);
1479   g_free (basename);
1480   g_free (dirname);
1481
1482   mimetype = g_strdup_printf ("application/x-extension-%s", extension);
1483   
1484   if (!g_file_test (filename, G_FILE_TEST_EXISTS)) 
1485     {
1486       char *contents;
1487
1488       contents =
1489         g_strdup_printf ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
1490                          "<mime-info xmlns=\"http://www.freedesktop.org/standards/shared-mime-info\">\n"
1491                          " <mime-type type=\"%s\">\n"
1492                          "  <comment>%s document</comment>\n"
1493                          "  <glob pattern=\"*.%s\"/>\n"
1494                          " </mime-type>\n"
1495                          "</mime-info>\n", mimetype, extension, extension);
1496
1497       g_file_set_contents (filename, contents, -1, NULL);
1498       g_free (contents);
1499
1500       run_update_command ("update-mime-database", "mime");
1501     }
1502   g_free (filename);
1503   
1504   res = g_desktop_app_info_set_as_default_for_type (appinfo,
1505                                                     mimetype,
1506                                                     error);
1507
1508   g_free (mimetype);
1509   
1510   return res;
1511 }
1512
1513 static gboolean
1514 g_desktop_app_info_add_supports_type (GAppInfo    *appinfo,
1515                                       const char  *content_type,
1516                                       GError     **error)
1517 {
1518   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
1519
1520   if (!g_desktop_app_info_ensure_saved (G_DESKTOP_APP_INFO (info), error))
1521     return FALSE;  
1522   
1523   return update_mimeapps_list (info->desktop_id, content_type, FALSE, TRUE, FALSE, error);
1524 }
1525
1526 static gboolean
1527 g_desktop_app_info_can_remove_supports_type (GAppInfo *appinfo)
1528 {
1529   return TRUE;
1530 }
1531
1532 static gboolean
1533 g_desktop_app_info_remove_supports_type (GAppInfo    *appinfo,
1534                                          const char  *content_type,
1535                                          GError     **error)
1536 {
1537   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
1538
1539   if (!g_desktop_app_info_ensure_saved (G_DESKTOP_APP_INFO (info), error))
1540     return FALSE;
1541   
1542   return update_mimeapps_list (info->desktop_id, content_type, FALSE, FALSE, TRUE, error);
1543 }
1544
1545 static gboolean
1546 g_desktop_app_info_ensure_saved (GDesktopAppInfo  *info,
1547                                  GError          **error)
1548 {
1549   GKeyFile *key_file;
1550   char *dirname;
1551   char *filename;
1552   char *data, *desktop_id;
1553   gsize data_size;
1554   int fd;
1555   gboolean res;
1556   
1557   if (info->filename != NULL)
1558     return TRUE;
1559
1560   /* This is only used for object created with
1561    * g_app_info_create_from_commandline. All other
1562    * object should have a filename
1563    */
1564   
1565   dirname = ensure_dir (APP_DIR, error);
1566   if (!dirname)
1567     return FALSE;
1568   
1569   key_file = g_key_file_new ();
1570
1571   g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
1572                          "Encoding", "UTF-8");
1573   g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
1574                          G_KEY_FILE_DESKTOP_KEY_VERSION, "1.0");
1575   g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
1576                          G_KEY_FILE_DESKTOP_KEY_TYPE,
1577                          G_KEY_FILE_DESKTOP_TYPE_APPLICATION);
1578   if (info->terminal) 
1579     g_key_file_set_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP,
1580                             G_KEY_FILE_DESKTOP_KEY_TERMINAL, TRUE);
1581
1582   g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
1583                          G_KEY_FILE_DESKTOP_KEY_EXEC, info->exec);
1584
1585   g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
1586                          G_KEY_FILE_DESKTOP_KEY_NAME, info->name);
1587
1588   g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
1589                          G_KEY_FILE_DESKTOP_KEY_COMMENT, info->comment);
1590   
1591   g_key_file_set_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP,
1592                           G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY, TRUE);
1593
1594   data = g_key_file_to_data (key_file, &data_size, NULL);
1595   g_key_file_free (key_file);
1596
1597   desktop_id = g_strdup_printf ("userapp-%s-XXXXXX.desktop", info->name);
1598   filename = g_build_filename (dirname, desktop_id, NULL);
1599   g_free (desktop_id);
1600   g_free (dirname);
1601   
1602   fd = g_mkstemp (filename);
1603   if (fd == -1)
1604     {
1605       char *display_name;
1606
1607       display_name = g_filename_display_name (filename);
1608       g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
1609                    _("Can't create user desktop file %s"), display_name);
1610       g_free (display_name);
1611       g_free (filename);
1612       g_free (data);
1613       return FALSE;
1614     }
1615
1616   desktop_id = g_path_get_basename (filename);
1617
1618   close (fd);
1619   
1620   res = g_file_set_contents (filename, data, data_size, error);
1621   if (!res)
1622     {
1623       g_free (desktop_id);
1624       g_free (filename);
1625       return FALSE;
1626     }
1627
1628   info->filename = filename;
1629   info->desktop_id = desktop_id;
1630   
1631   run_update_command ("update-desktop-database", "applications");
1632   
1633   return TRUE;
1634 }
1635
1636 static gboolean
1637 g_desktop_app_info_can_delete (GAppInfo *appinfo)
1638 {
1639   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
1640
1641   if (info->filename)
1642     {
1643       if (strstr (info->filename, "/userapp-"))
1644         return g_access (info->filename, W_OK) == 0;
1645     }
1646
1647   return FALSE;
1648 }
1649
1650 static gboolean
1651 g_desktop_app_info_delete (GAppInfo *appinfo)
1652 {
1653   GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
1654   
1655   if (info->filename)
1656     { 
1657       if (g_remove (info->filename) == 0)
1658         {
1659           update_mimeapps_list (info->desktop_id, NULL, FALSE, FALSE, FALSE, NULL);
1660
1661           g_free (info->filename);
1662           info->filename = NULL;
1663           g_free (info->desktop_id);
1664           info->desktop_id = NULL;
1665
1666           return TRUE;
1667         }
1668     }
1669
1670   return FALSE;
1671 }
1672
1673 /**
1674  * g_app_info_create_from_commandline:
1675  * @commandline: the commandline to use
1676  * @application_name: the application name, or %NULL to use @commandline
1677  * @flags: flags that can specify details of the created #GAppInfo
1678  * @error: a #GError location to store the error occuring, %NULL to ignore.
1679  *
1680  * Creates a new #GAppInfo from the given information.
1681  *
1682  * Returns: new #GAppInfo for given command.
1683  **/
1684 GAppInfo *
1685 g_app_info_create_from_commandline (const char           *commandline,
1686                                     const char           *application_name,
1687                                     GAppInfoCreateFlags   flags,
1688                                     GError              **error)
1689 {
1690   char **split;
1691   char *basename;
1692   GDesktopAppInfo *info;
1693
1694   info = g_object_new (G_TYPE_DESKTOP_APP_INFO, NULL);
1695
1696   info->filename = NULL;
1697   info->desktop_id = NULL;
1698   
1699   info->terminal = flags & G_APP_INFO_CREATE_NEEDS_TERMINAL;
1700   info->startup_notify = FALSE;
1701   info->hidden = FALSE;
1702   if (flags & G_APP_INFO_CREATE_SUPPORTS_URIS)
1703     info->exec = g_strconcat (commandline, " %u", NULL);
1704   else
1705     info->exec = g_strconcat (commandline, " %f", NULL);
1706   info->nodisplay = TRUE;
1707   info->binary = binary_from_exec (info->exec);
1708   
1709   if (application_name)
1710     info->name = g_strdup (application_name);
1711   else
1712     {
1713       /* FIXME: this should be more robust. Maybe g_shell_parse_argv and use argv[0] */
1714       split = g_strsplit (commandline, " ", 2);
1715       basename = g_path_get_basename (split[0]);
1716       g_strfreev (split);
1717       info->name = basename;
1718       if (info->name == NULL)
1719         info->name = g_strdup ("custom");
1720     }
1721   info->comment = g_strdup_printf (_("Custom definition for %s"), info->name);
1722   
1723   return G_APP_INFO (info);
1724 }
1725
1726 static void
1727 g_desktop_app_info_iface_init (GAppInfoIface *iface)
1728 {
1729   iface->dup = g_desktop_app_info_dup;
1730   iface->equal = g_desktop_app_info_equal;
1731   iface->get_id = g_desktop_app_info_get_id;
1732   iface->get_name = g_desktop_app_info_get_name;
1733   iface->get_description = g_desktop_app_info_get_description;
1734   iface->get_executable = g_desktop_app_info_get_executable;
1735   iface->get_icon = g_desktop_app_info_get_icon;
1736   iface->launch = g_desktop_app_info_launch;
1737   iface->supports_uris = g_desktop_app_info_supports_uris;
1738   iface->supports_files = g_desktop_app_info_supports_files;
1739   iface->launch_uris = g_desktop_app_info_launch_uris;
1740   iface->should_show = g_desktop_app_info_should_show;
1741   iface->set_as_default_for_type = g_desktop_app_info_set_as_default_for_type;
1742   iface->set_as_default_for_extension = g_desktop_app_info_set_as_default_for_extension;
1743   iface->add_supports_type = g_desktop_app_info_add_supports_type;
1744   iface->can_remove_supports_type = g_desktop_app_info_can_remove_supports_type;
1745   iface->remove_supports_type = g_desktop_app_info_remove_supports_type;
1746   iface->can_delete = g_desktop_app_info_can_delete;
1747   iface->do_delete = g_desktop_app_info_delete;
1748 }
1749
1750 static gboolean
1751 app_info_in_list (GAppInfo *info, 
1752                   GList    *list)
1753 {
1754   while (list != NULL)
1755     {
1756       if (g_app_info_equal (info, list->data))
1757         return TRUE;
1758       list = list->next;
1759     }
1760   return FALSE;
1761 }
1762
1763
1764 /**
1765  * g_app_info_get_all_for_type:
1766  * @content_type: the content type to find a #GAppInfo for
1767  * 
1768  * Gets a list of all #GAppInfo s for a given content type.
1769  *
1770  * Returns: #GList of #GAppInfo s for given @content_type
1771  *    or %NULL on error.
1772  **/
1773 GList *
1774 g_app_info_get_all_for_type (const char *content_type)
1775 {
1776   GList *desktop_entries, *l;
1777   GList *infos;
1778   GDesktopAppInfo *info;
1779
1780   g_return_val_if_fail (content_type != NULL, NULL);
1781   
1782   desktop_entries = get_all_desktop_entries_for_mime_type (content_type, NULL);
1783
1784   infos = NULL;
1785   for (l = desktop_entries; l != NULL; l = l->next)
1786     {
1787       char *desktop_entry = l->data;
1788
1789       info = g_desktop_app_info_new (desktop_entry);
1790       if (info)
1791         {
1792           if (app_info_in_list (G_APP_INFO (info), infos))
1793             g_object_unref (info);
1794           else
1795             infos = g_list_prepend (infos, info);
1796         }
1797       g_free (desktop_entry);
1798     }
1799
1800   g_list_free (desktop_entries);
1801   
1802   return g_list_reverse (infos);
1803 }
1804
1805 /**
1806  * g_app_info_reset_type_associations:
1807  * @content_type: a content type 
1808  *
1809  * Removes all changes to the type associations done by
1810  * g_app_info_set_as_default_for_type(), 
1811  * g_app_info_set_as_default_for_extension(), 
1812  * g_app_info_add_supports_type() of g_app_info_remove_supports_type().
1813  *
1814  * Since: 2.20
1815  */
1816 void
1817 g_app_info_reset_type_associations (const char *content_type)
1818 {
1819   update_mimeapps_list (NULL, content_type, FALSE, FALSE, FALSE, NULL);
1820 }
1821
1822 /**
1823  * g_app_info_get_default_for_type:
1824  * @content_type: the content type to find a #GAppInfo for
1825  * @must_support_uris: if %TRUE, the #GAppInfo is expected to
1826  *     support URIs
1827  * 
1828  * Gets the #GAppInfo that correspond to a given content type.
1829  *
1830  * Returns: #GAppInfo for given @content_type or %NULL on error.
1831  **/
1832 GAppInfo *
1833 g_app_info_get_default_for_type (const char *content_type,
1834                                  gboolean    must_support_uris)
1835 {
1836   GList *desktop_entries, *l;
1837   GAppInfo *info;
1838
1839   g_return_val_if_fail (content_type != NULL, NULL);
1840   
1841   desktop_entries = get_all_desktop_entries_for_mime_type (content_type, NULL);
1842
1843   info = NULL;
1844   for (l = desktop_entries; l != NULL; l = l->next)
1845     {
1846       char *desktop_entry = l->data;
1847
1848       info = (GAppInfo *)g_desktop_app_info_new (desktop_entry);
1849       if (info)
1850         {
1851           if (must_support_uris && !g_app_info_supports_uris (info))
1852             {
1853               g_object_unref (info);
1854               info = NULL;
1855             }
1856           else
1857             break;
1858         }
1859     }
1860   
1861   g_list_foreach  (desktop_entries, (GFunc)g_free, NULL);
1862   g_list_free (desktop_entries);
1863   
1864   return info;
1865 }
1866
1867 /**
1868  * g_app_info_get_default_for_uri_scheme:
1869  * @uri_scheme: a string containing a URI scheme.
1870  *
1871  * Gets the default application for launching applications 
1872  * using this URI scheme. A URI scheme is the initial part 
1873  * of the URI, up to but not including the ':', e.g. "http", 
1874  * "ftp" or "sip".
1875  * 
1876  * Returns: #GAppInfo for given @uri_scheme or %NULL on error.
1877  **/
1878 GAppInfo *
1879 g_app_info_get_default_for_uri_scheme (const char *uri_scheme)
1880 {
1881   static gsize lookup = 0;
1882   
1883   if (g_once_init_enter (&lookup))
1884     {
1885       gsize setup_value = 1;
1886       GDesktopAppInfoLookup *lookup_instance;
1887       const char *use_this;
1888       GIOExtensionPoint *ep;
1889       GIOExtension *extension;
1890       GList *l;
1891
1892       use_this = g_getenv ("GIO_USE_URI_ASSOCIATION");
1893       
1894       /* Ensure vfs in modules loaded */
1895       _g_io_modules_ensure_loaded ();
1896       
1897       ep = g_io_extension_point_lookup (G_DESKTOP_APP_INFO_LOOKUP_EXTENSION_POINT_NAME);
1898
1899       lookup_instance = NULL;
1900       if (use_this)
1901         {
1902           extension = g_io_extension_point_get_extension_by_name (ep, use_this);
1903           if (extension)
1904             lookup_instance = g_object_new (g_io_extension_get_type (extension), NULL);
1905         }
1906       
1907       if (lookup_instance == NULL)
1908         {
1909           for (l = g_io_extension_point_get_extensions (ep); l != NULL; l = l->next)
1910             {
1911               extension = l->data;
1912               lookup_instance = g_object_new (g_io_extension_get_type (extension), NULL);
1913               if (lookup_instance != NULL)
1914                 break;
1915             }
1916         }
1917
1918       if (lookup_instance != NULL)
1919         setup_value = (gsize)lookup_instance;
1920       
1921       g_once_init_leave (&lookup, setup_value);
1922     }
1923
1924   if (lookup == 1)
1925     return NULL;
1926
1927   return g_desktop_app_info_lookup_get_default_for_uri_scheme (G_DESKTOP_APP_INFO_LOOKUP (lookup),
1928                                                                uri_scheme);
1929 }
1930
1931
1932 static void
1933 get_apps_from_dir (GHashTable *apps, 
1934                    const char *dirname, 
1935                    const char *prefix)
1936 {
1937   GDir *dir;
1938   const char *basename;
1939   char *filename, *subprefix, *desktop_id;
1940   gboolean hidden;
1941   GDesktopAppInfo *appinfo;
1942   
1943   dir = g_dir_open (dirname, 0, NULL);
1944   if (dir)
1945     {
1946       while ((basename = g_dir_read_name (dir)) != NULL)
1947         {
1948           filename = g_build_filename (dirname, basename, NULL);
1949           if (g_str_has_suffix (basename, ".desktop"))
1950             {
1951               desktop_id = g_strconcat (prefix, basename, NULL);
1952
1953               /* Use _extended so we catch NULLs too (hidden) */
1954               if (!g_hash_table_lookup_extended (apps, desktop_id, NULL, NULL))
1955                 {
1956                   appinfo = g_desktop_app_info_new_from_filename (filename);
1957
1958                   if (appinfo && g_desktop_app_info_get_is_hidden (appinfo))
1959                     {
1960                       g_object_unref (appinfo);
1961                       appinfo = NULL;
1962                       hidden = TRUE;
1963                     }
1964                                       
1965                   if (appinfo || hidden)
1966                     {
1967                       g_hash_table_insert (apps, g_strdup (desktop_id), appinfo);
1968
1969                       if (appinfo)
1970                         {
1971                           /* Reuse instead of strdup here */
1972                           appinfo->desktop_id = desktop_id;
1973                           desktop_id = NULL;
1974                         }
1975                     }
1976                 }
1977               g_free (desktop_id);
1978             }
1979           else
1980             {
1981               if (g_file_test (filename, G_FILE_TEST_IS_DIR))
1982                 {
1983                   subprefix = g_strconcat (prefix, basename, "-", NULL);
1984                   get_apps_from_dir (apps, filename, subprefix);
1985                   g_free (subprefix);
1986                 }
1987             }
1988           g_free (filename);
1989         }
1990       g_dir_close (dir);
1991     }
1992 }
1993
1994
1995 /**
1996  * g_app_info_get_all:
1997  *
1998  * Gets a list of all of the applications currently registered 
1999  * on this system.
2000  * 
2001  * For desktop files, this includes applications that have 
2002  * <literal>NoDisplay=true</literal> set or are excluded from 
2003  * display by means of <literal>OnlyShowIn</literal> or
2004  * <literal>NotShowIn</literal>. See g_app_info_should_show().
2005  * The returned list does not include applications which have
2006  * the <literal>Hidden</literal> key set. 
2007  * 
2008  * Returns: a newly allocated #GList of references to #GAppInfo<!---->s.
2009  **/
2010 GList *
2011 g_app_info_get_all (void)
2012 {
2013   const char * const *dirs;
2014   GHashTable *apps;
2015   GHashTableIter iter;
2016   gpointer value;
2017   int i;
2018   GList *infos;
2019
2020   dirs = get_applications_search_path ();
2021
2022   apps = g_hash_table_new_full (g_str_hash, g_str_equal,
2023                                 g_free, NULL);
2024
2025   
2026   for (i = 0; dirs[i] != NULL; i++)
2027     get_apps_from_dir (apps, dirs[i], "");
2028
2029
2030   infos = NULL;
2031   g_hash_table_iter_init (&iter, apps);
2032   while (g_hash_table_iter_next (&iter, NULL, &value))
2033     {
2034       if (value)
2035         infos = g_list_prepend (infos, value);
2036     }
2037
2038   g_hash_table_destroy (apps);
2039
2040   return g_list_reverse (infos);
2041 }
2042
2043 /* Cacheing of mimeinfo.cache and defaults.list files */
2044
2045 typedef struct {
2046   char *path;
2047   GHashTable *mime_info_cache_map;
2048   GHashTable *defaults_list_map;
2049   GHashTable *mimeapps_list_added_map;
2050   GHashTable *mimeapps_list_removed_map;
2051   time_t mime_info_cache_timestamp;
2052   time_t defaults_list_timestamp;
2053   time_t mimeapps_list_timestamp;
2054 } MimeInfoCacheDir;
2055
2056 typedef struct {
2057   GList *dirs;                       /* mimeinfo.cache and defaults.list */
2058   GHashTable *global_defaults_cache; /* global results of defaults.list lookup and validation */
2059   time_t last_stat_time;
2060   guint should_ping_mime_monitor : 1;
2061 } MimeInfoCache;
2062
2063 static MimeInfoCache *mime_info_cache = NULL;
2064 G_LOCK_DEFINE_STATIC (mime_info_cache);
2065
2066 static void mime_info_cache_dir_add_desktop_entries (MimeInfoCacheDir  *dir,
2067                                                      const char        *mime_type,
2068                                                      char             **new_desktop_file_ids);
2069
2070 static MimeInfoCache * mime_info_cache_new (void);
2071
2072 static void
2073 destroy_info_cache_value (gpointer  key, 
2074                           GList    *value, 
2075                           gpointer  data)
2076 {
2077   g_list_foreach (value, (GFunc)g_free, NULL);
2078   g_list_free (value);
2079 }
2080
2081 static void
2082 destroy_info_cache_map (GHashTable *info_cache_map)
2083 {
2084   g_hash_table_foreach (info_cache_map, (GHFunc)destroy_info_cache_value, NULL);
2085   g_hash_table_destroy (info_cache_map);
2086 }
2087
2088 static gboolean
2089 mime_info_cache_dir_out_of_date (MimeInfoCacheDir *dir,
2090                                  const char       *cache_file,
2091                                  time_t           *timestamp)
2092 {
2093   struct stat buf;
2094   char *filename;
2095   
2096   filename = g_build_filename (dir->path, cache_file, NULL);
2097   
2098   if (g_stat (filename, &buf) < 0)
2099     {
2100       g_free (filename);
2101       return TRUE;
2102     }
2103   g_free (filename);
2104
2105   if (buf.st_mtime != *timestamp) 
2106     return TRUE;
2107   
2108   return FALSE;
2109 }
2110
2111 /* Call with lock held */
2112 static gboolean
2113 remove_all (gpointer  key,
2114             gpointer  value,
2115             gpointer  user_data)
2116 {
2117   return TRUE;
2118 }
2119
2120
2121 static void
2122 mime_info_cache_blow_global_cache (void)
2123 {
2124   g_hash_table_foreach_remove (mime_info_cache->global_defaults_cache,
2125                                remove_all, NULL);
2126 }
2127
2128 static void
2129 mime_info_cache_dir_init (MimeInfoCacheDir *dir)
2130 {
2131   GError *load_error;
2132   GKeyFile *key_file;
2133   gchar *filename, **mime_types;
2134   int i;
2135   struct stat buf;
2136   
2137   load_error = NULL;
2138   mime_types = NULL;
2139   
2140   if (dir->mime_info_cache_map != NULL &&
2141       !mime_info_cache_dir_out_of_date (dir, "mimeinfo.cache",
2142                                         &dir->mime_info_cache_timestamp))
2143     return;
2144   
2145   if (dir->mime_info_cache_map != NULL)
2146     destroy_info_cache_map (dir->mime_info_cache_map);
2147   
2148   dir->mime_info_cache_map = g_hash_table_new_full (g_str_hash, g_str_equal,
2149                                                     (GDestroyNotify) g_free,
2150                                                     NULL);
2151   
2152   key_file = g_key_file_new ();
2153   
2154   filename = g_build_filename (dir->path, "mimeinfo.cache", NULL);
2155   
2156   if (g_stat (filename, &buf) < 0)
2157     goto error;
2158   
2159   if (dir->mime_info_cache_timestamp > 0) 
2160     mime_info_cache->should_ping_mime_monitor = TRUE;
2161   
2162   dir->mime_info_cache_timestamp = buf.st_mtime;
2163   
2164   g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, &load_error);
2165   
2166   g_free (filename);
2167   filename = NULL;
2168   
2169   if (load_error != NULL)
2170     goto error;
2171   
2172   mime_types = g_key_file_get_keys (key_file, MIME_CACHE_GROUP,
2173                                     NULL, &load_error);
2174   
2175   if (load_error != NULL)
2176     goto error;
2177   
2178   for (i = 0; mime_types[i] != NULL; i++)
2179     {
2180       gchar **desktop_file_ids;
2181       char *unaliased_type;
2182       desktop_file_ids = g_key_file_get_string_list (key_file,
2183                                                      MIME_CACHE_GROUP,
2184                                                      mime_types[i],
2185                                                      NULL,
2186                                                      NULL);
2187       
2188       if (desktop_file_ids == NULL)
2189         continue;
2190
2191       unaliased_type = _g_unix_content_type_unalias (mime_types[i]);
2192       mime_info_cache_dir_add_desktop_entries (dir,
2193                                                unaliased_type,
2194                                                desktop_file_ids);
2195       g_free (unaliased_type);
2196     
2197       g_strfreev (desktop_file_ids);
2198     }
2199   
2200   g_strfreev (mime_types);
2201   g_key_file_free (key_file);
2202   
2203   return;
2204  error:
2205   g_free (filename);
2206   g_key_file_free (key_file);
2207   
2208   if (mime_types != NULL)
2209     g_strfreev (mime_types);
2210   
2211   if (load_error)
2212     g_error_free (load_error);
2213 }
2214
2215 static void
2216 mime_info_cache_dir_init_defaults_list (MimeInfoCacheDir *dir)
2217 {
2218   GKeyFile *key_file;
2219   GError *load_error;
2220   gchar *filename, **mime_types;
2221   char *unaliased_type;
2222   char **desktop_file_ids;
2223   int i;
2224   struct stat buf;
2225
2226   load_error = NULL;
2227   mime_types = NULL;
2228
2229   if (dir->defaults_list_map != NULL &&
2230       !mime_info_cache_dir_out_of_date (dir, "defaults.list",
2231                                         &dir->defaults_list_timestamp))
2232     return;
2233   
2234   if (dir->defaults_list_map != NULL)
2235     g_hash_table_destroy (dir->defaults_list_map);
2236   dir->defaults_list_map = g_hash_table_new_full (g_str_hash, g_str_equal,
2237                                                   g_free, (GDestroyNotify)g_strfreev);
2238   
2239
2240   key_file = g_key_file_new ();
2241   
2242   filename = g_build_filename (dir->path, "defaults.list", NULL);
2243   if (g_stat (filename, &buf) < 0)
2244     goto error;
2245
2246   if (dir->defaults_list_timestamp > 0) 
2247     mime_info_cache->should_ping_mime_monitor = TRUE;
2248
2249   dir->defaults_list_timestamp = buf.st_mtime;
2250
2251   g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, &load_error);
2252   g_free (filename);
2253   filename = NULL;
2254
2255   if (load_error != NULL)
2256     goto error;
2257
2258   mime_types = g_key_file_get_keys (key_file, DEFAULT_APPLICATIONS_GROUP,
2259                                     NULL, NULL);
2260   if (mime_types != NULL)
2261     {
2262       for (i = 0; mime_types[i] != NULL; i++)
2263         {
2264           desktop_file_ids = g_key_file_get_string_list (key_file,
2265                                                          DEFAULT_APPLICATIONS_GROUP,
2266                                                          mime_types[i],
2267                                                          NULL,
2268                                                          NULL);
2269           if (desktop_file_ids == NULL)
2270             continue;
2271           
2272           unaliased_type = _g_unix_content_type_unalias (mime_types[i]);
2273           g_hash_table_replace (dir->defaults_list_map,
2274                                 unaliased_type,
2275                                 desktop_file_ids);
2276         }
2277       
2278       g_strfreev (mime_types);
2279     }
2280
2281   g_key_file_free (key_file);
2282   return;
2283   
2284  error:
2285   g_free (filename);
2286   g_key_file_free (key_file);
2287   
2288   if (mime_types != NULL)
2289     g_strfreev (mime_types);
2290   
2291   if (load_error)
2292     g_error_free (load_error);
2293 }
2294
2295 static void
2296 mime_info_cache_dir_init_mimeapps_list (MimeInfoCacheDir *dir)
2297 {
2298   GKeyFile *key_file;
2299   GError *load_error;
2300   gchar *filename, **mime_types;
2301   char *unaliased_type;
2302   char **desktop_file_ids;
2303   int i;
2304   struct stat buf;
2305
2306   load_error = NULL;
2307   mime_types = NULL;
2308
2309   if (dir->mimeapps_list_added_map != NULL &&
2310       !mime_info_cache_dir_out_of_date (dir, "mimeapps.list",
2311                                         &dir->mimeapps_list_timestamp))
2312     return;
2313   
2314   if (dir->mimeapps_list_added_map != NULL)
2315     g_hash_table_destroy (dir->mimeapps_list_added_map);
2316   dir->mimeapps_list_added_map = g_hash_table_new_full (g_str_hash, g_str_equal,
2317                                                         g_free, (GDestroyNotify)g_strfreev);
2318   
2319   if (dir->mimeapps_list_removed_map != NULL)
2320     g_hash_table_destroy (dir->mimeapps_list_removed_map);
2321   dir->mimeapps_list_removed_map = g_hash_table_new_full (g_str_hash, g_str_equal,
2322                                                           g_free, (GDestroyNotify)g_strfreev);
2323
2324   key_file = g_key_file_new ();
2325   
2326   filename = g_build_filename (dir->path, "mimeapps.list", NULL);
2327   if (g_stat (filename, &buf) < 0)
2328     goto error;
2329
2330   if (dir->mimeapps_list_timestamp > 0) 
2331     mime_info_cache->should_ping_mime_monitor = TRUE;
2332
2333   dir->mimeapps_list_timestamp = buf.st_mtime;
2334
2335   g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, &load_error);
2336   g_free (filename);
2337   filename = NULL;
2338
2339   if (load_error != NULL)
2340     goto error;
2341
2342   mime_types = g_key_file_get_keys (key_file, ADDED_ASSOCIATIONS_GROUP,
2343                                     NULL, NULL);
2344   if (mime_types != NULL)
2345     {
2346       for (i = 0; mime_types[i] != NULL; i++)
2347         {
2348           desktop_file_ids = g_key_file_get_string_list (key_file,
2349                                                          ADDED_ASSOCIATIONS_GROUP,
2350                                                          mime_types[i],
2351                                                          NULL,
2352                                                          NULL);
2353           if (desktop_file_ids == NULL)
2354             continue;
2355           
2356           unaliased_type = _g_unix_content_type_unalias (mime_types[i]);
2357           g_hash_table_replace (dir->mimeapps_list_added_map,
2358                                 unaliased_type,
2359                                 desktop_file_ids);
2360         }
2361       
2362       g_strfreev (mime_types);
2363     }
2364
2365   mime_types = g_key_file_get_keys (key_file, REMOVED_ASSOCIATIONS_GROUP,
2366                                     NULL, NULL);
2367   if (mime_types != NULL)
2368     {
2369       for (i = 0; mime_types[i] != NULL; i++)
2370         {
2371           desktop_file_ids = g_key_file_get_string_list (key_file,
2372                                                          REMOVED_ASSOCIATIONS_GROUP,
2373                                                          mime_types[i],
2374                                                          NULL,
2375                                                          NULL);
2376           if (desktop_file_ids == NULL)
2377             continue;
2378           
2379           unaliased_type = _g_unix_content_type_unalias (mime_types[i]);
2380           g_hash_table_replace (dir->mimeapps_list_removed_map,
2381                                 unaliased_type,
2382                                 desktop_file_ids);
2383         }
2384       
2385       g_strfreev (mime_types);
2386     }
2387
2388   g_key_file_free (key_file);
2389   return;
2390   
2391  error:
2392   g_free (filename);
2393   g_key_file_free (key_file);
2394   
2395   if (mime_types != NULL)
2396     g_strfreev (mime_types);
2397   
2398   if (load_error)
2399     g_error_free (load_error);
2400 }
2401
2402 static MimeInfoCacheDir *
2403 mime_info_cache_dir_new (const char *path)
2404 {
2405   MimeInfoCacheDir *dir;
2406
2407   dir = g_new0 (MimeInfoCacheDir, 1);
2408   dir->path = g_strdup (path);
2409   
2410   return dir;
2411 }
2412
2413 static void
2414 mime_info_cache_dir_free (MimeInfoCacheDir *dir)
2415 {
2416   if (dir == NULL)
2417     return;
2418   
2419   if (dir->mime_info_cache_map != NULL)
2420     {
2421       destroy_info_cache_map (dir->mime_info_cache_map);
2422       dir->mime_info_cache_map = NULL;
2423       
2424   }
2425   
2426   if (dir->defaults_list_map != NULL)
2427     {
2428       g_hash_table_destroy (dir->defaults_list_map);
2429       dir->defaults_list_map = NULL;
2430     }
2431   
2432   if (dir->mimeapps_list_added_map != NULL)
2433     {
2434       g_hash_table_destroy (dir->mimeapps_list_added_map);
2435       dir->mimeapps_list_added_map = NULL;
2436     }
2437   
2438   if (dir->mimeapps_list_removed_map != NULL)
2439     {
2440       g_hash_table_destroy (dir->mimeapps_list_removed_map);
2441       dir->mimeapps_list_removed_map = NULL;
2442     }
2443   
2444   g_free (dir);
2445 }
2446
2447 static void
2448 mime_info_cache_dir_add_desktop_entries (MimeInfoCacheDir  *dir,
2449                                          const char        *mime_type,
2450                                          char             **new_desktop_file_ids)
2451 {
2452   GList *desktop_file_ids;
2453   int i;
2454   
2455   desktop_file_ids = g_hash_table_lookup (dir->mime_info_cache_map,
2456                                           mime_type);
2457   
2458   for (i = 0; new_desktop_file_ids[i] != NULL; i++)
2459     {
2460       if (!g_list_find (desktop_file_ids, new_desktop_file_ids[i]))
2461         desktop_file_ids = g_list_append (desktop_file_ids,
2462                                           g_strdup (new_desktop_file_ids[i]));
2463     }
2464   
2465   g_hash_table_insert (dir->mime_info_cache_map, g_strdup (mime_type), desktop_file_ids);
2466 }
2467
2468 static void
2469 mime_info_cache_init_dir_lists (void)
2470 {
2471   const char * const *dirs;
2472   int i;
2473   
2474   mime_info_cache = mime_info_cache_new ();
2475   
2476   dirs = get_applications_search_path ();
2477   
2478   for (i = 0; dirs[i] != NULL; i++)
2479     {
2480       MimeInfoCacheDir *dir;
2481       
2482       dir = mime_info_cache_dir_new (dirs[i]);
2483       
2484       if (dir != NULL)
2485         {
2486           mime_info_cache_dir_init (dir);
2487           mime_info_cache_dir_init_defaults_list (dir);
2488           mime_info_cache_dir_init_mimeapps_list (dir);
2489           
2490           mime_info_cache->dirs = g_list_append (mime_info_cache->dirs, dir);
2491         }
2492     }
2493 }
2494
2495 static void
2496 mime_info_cache_update_dir_lists (void)
2497 {
2498   GList *tmp;
2499   
2500   tmp = mime_info_cache->dirs;
2501   
2502   while (tmp != NULL)
2503     {
2504       MimeInfoCacheDir *dir = (MimeInfoCacheDir *) tmp->data;
2505
2506       /* No need to do this if we had file monitors... */
2507       mime_info_cache_blow_global_cache ();
2508       mime_info_cache_dir_init (dir);
2509       mime_info_cache_dir_init_defaults_list (dir);
2510       mime_info_cache_dir_init_mimeapps_list (dir);
2511       
2512       tmp = tmp->next;
2513     }
2514 }
2515
2516 static void
2517 mime_info_cache_init (void)
2518 {
2519   G_LOCK (mime_info_cache);
2520   if (mime_info_cache == NULL)
2521     mime_info_cache_init_dir_lists ();
2522   else
2523     {
2524       time_t now;
2525       
2526       time (&now);
2527       if (now >= mime_info_cache->last_stat_time + 10)
2528         {
2529           mime_info_cache_update_dir_lists ();
2530           mime_info_cache->last_stat_time = now;
2531         }
2532     }
2533   
2534   if (mime_info_cache->should_ping_mime_monitor)
2535     {
2536       /* g_idle_add (emit_mime_changed, NULL); */
2537       mime_info_cache->should_ping_mime_monitor = FALSE;
2538     }
2539   
2540   G_UNLOCK (mime_info_cache);
2541 }
2542
2543 static MimeInfoCache *
2544 mime_info_cache_new (void)
2545 {
2546   MimeInfoCache *cache;
2547   
2548   cache = g_new0 (MimeInfoCache, 1);
2549   
2550   cache->global_defaults_cache = g_hash_table_new_full (g_str_hash, g_str_equal,
2551                                                         (GDestroyNotify) g_free,
2552                                                         (GDestroyNotify) g_free);
2553   return cache;
2554 }
2555
2556 static void
2557 mime_info_cache_free (MimeInfoCache *cache)
2558 {
2559   if (cache == NULL)
2560     return;
2561   
2562   g_list_foreach (cache->dirs,
2563                   (GFunc) mime_info_cache_dir_free,
2564                   NULL);
2565   g_list_free (cache->dirs);
2566   g_hash_table_destroy (cache->global_defaults_cache);
2567   g_free (cache);
2568 }
2569
2570 /**
2571  * mime_info_cache_reload:
2572  * @dir: directory path which needs reloading.
2573  * 
2574  * Reload the mime information for the @dir.
2575  */
2576 static void
2577 mime_info_cache_reload (const char *dir)
2578 {
2579   /* FIXME: just reload the dir that needs reloading,
2580    * don't blow the whole cache
2581    */
2582   if (mime_info_cache != NULL)
2583     {
2584       G_LOCK (mime_info_cache);
2585       mime_info_cache_free (mime_info_cache);
2586       mime_info_cache = NULL;
2587       G_UNLOCK (mime_info_cache);
2588     }
2589 }
2590
2591 static GList *
2592 append_desktop_entry (GList      *list, 
2593                       const char *desktop_entry,
2594                       GList      *removed_entries)
2595 {
2596   /* Add if not already in list, and valid */
2597   if (!g_list_find_custom (list, desktop_entry, (GCompareFunc) strcmp) &&
2598       !g_list_find_custom (removed_entries, desktop_entry, (GCompareFunc) strcmp))
2599     list = g_list_prepend (list, g_strdup (desktop_entry));
2600   
2601   return list;
2602 }
2603
2604 /**
2605  * get_all_desktop_entries_for_mime_type:
2606  * @mime_type: a mime type.
2607  * @except: NULL or a strv list
2608  *
2609  * Returns all the desktop ids for @mime_type. The desktop files
2610  * are listed in an order so that default applications are listed before
2611  * non-default ones, and handlers for inherited mimetypes are listed
2612  * after the base ones.
2613  *
2614  * Optionally doesn't list the desktop ids given in the @except 
2615  *
2616  * Return value: a #GList containing the desktop ids which claim
2617  *    to handle @mime_type.
2618  */
2619 static GList *
2620 get_all_desktop_entries_for_mime_type (const char *base_mime_type,
2621                                        const char **except)
2622 {
2623   GList *desktop_entries, *removed_entries, *list, *dir_list, *tmp;
2624   MimeInfoCacheDir *dir;
2625   char *mime_type;
2626   char **mime_types;
2627   char **default_entries;
2628   char **removed_associations;
2629   int i, j, k;
2630   GPtrArray *array;
2631   char **anc;
2632   
2633   mime_info_cache_init ();
2634
2635   /* collect all ancestors */
2636   mime_types = _g_unix_content_type_get_parents (base_mime_type);
2637   array = g_ptr_array_new ();
2638   for (i = 0; mime_types[i]; i++)
2639     g_ptr_array_add (array, mime_types[i]);
2640   g_free (mime_types);
2641   for (i = 0; i < array->len; i++)
2642     {
2643       anc = _g_unix_content_type_get_parents (g_ptr_array_index (array, i));
2644       for (j = 0; anc[j]; j++)
2645         {
2646           for (k = 0; k < array->len; k++)
2647             {
2648               if (strcmp (anc[j], g_ptr_array_index (array, k)) == 0)
2649                 break;
2650             }
2651           if (k == array->len) /* not found */
2652             g_ptr_array_add (array, g_strdup (anc[j]));
2653         }
2654       g_strfreev (anc);
2655     }
2656   g_ptr_array_add (array, NULL);
2657   mime_types = (char **)g_ptr_array_free (array, FALSE);
2658
2659   G_LOCK (mime_info_cache);
2660   
2661   removed_entries = NULL;
2662   desktop_entries = NULL;
2663
2664   for (i = 0; except != NULL && except[i] != NULL; i++)
2665     removed_entries = g_list_prepend (removed_entries, g_strdup (except[i]));
2666   
2667   for (i = 0; mime_types[i] != NULL; i++)
2668     {
2669       mime_type = mime_types[i];
2670
2671       /* Go through all apps listed as defaults */
2672       for (dir_list = mime_info_cache->dirs;
2673            dir_list != NULL;
2674            dir_list = dir_list->next)
2675         {
2676           dir = dir_list->data;
2677
2678           /* First added associations from mimeapps.list */
2679           default_entries = g_hash_table_lookup (dir->mimeapps_list_added_map, mime_type);
2680           for (j = 0; default_entries != NULL && default_entries[j] != NULL; j++)
2681             desktop_entries = append_desktop_entry (desktop_entries, default_entries[j], removed_entries);
2682
2683           /* Then removed associations from mimeapps.list */
2684           removed_associations = g_hash_table_lookup (dir->mimeapps_list_removed_map, mime_type);
2685           for (j = 0; removed_associations != NULL && removed_associations[j] != NULL; j++)
2686             removed_entries = append_desktop_entry (removed_entries, removed_associations[j], NULL);
2687
2688           /* Then system defaults (or old per-user config) (using removed associations from this dir or earlier) */
2689           default_entries = g_hash_table_lookup (dir->defaults_list_map, mime_type);
2690           for (j = 0; default_entries != NULL && default_entries[j] != NULL; j++)
2691             desktop_entries = append_desktop_entry (desktop_entries, default_entries[j], removed_entries);
2692         }
2693
2694       /* Go through all entries that support the mimetype */
2695       for (dir_list = mime_info_cache->dirs;
2696            dir_list != NULL;
2697            dir_list = dir_list->next) 
2698         {
2699           dir = dir_list->data;
2700         
2701           list = g_hash_table_lookup (dir->mime_info_cache_map, mime_type);
2702           for (tmp = list; tmp != NULL; tmp = tmp->next)
2703             desktop_entries = append_desktop_entry (desktop_entries, tmp->data, removed_entries);
2704         }
2705     }
2706   
2707   G_UNLOCK (mime_info_cache);
2708
2709   g_strfreev (mime_types);
2710
2711   g_list_foreach (removed_entries, (GFunc)g_free, NULL);
2712   g_list_free (removed_entries);
2713   
2714   desktop_entries = g_list_reverse (desktop_entries);
2715   
2716   return desktop_entries;
2717 }
2718
2719 /* GDesktopAppInfoLookup interface: */
2720
2721 static void g_desktop_app_info_lookup_base_init (gpointer g_class);
2722 static void g_desktop_app_info_lookup_class_init (gpointer g_class,
2723                                                   gpointer class_data);
2724
2725 GType
2726 g_desktop_app_info_lookup_get_type (void)
2727 {
2728   static volatile gsize g_define_type_id__volatile = 0;
2729
2730   if (g_once_init_enter (&g_define_type_id__volatile))
2731     {
2732       const GTypeInfo desktop_app_info_lookup_info =
2733       {
2734         sizeof (GDesktopAppInfoLookupIface), /* class_size */
2735         g_desktop_app_info_lookup_base_init,   /* base_init */
2736         NULL,           /* base_finalize */
2737         g_desktop_app_info_lookup_class_init,
2738         NULL,           /* class_finalize */
2739         NULL,           /* class_data */
2740         0,
2741         0,              /* n_preallocs */
2742         NULL
2743       };
2744       GType g_define_type_id =
2745         g_type_register_static (G_TYPE_INTERFACE, I_("GDesktopAppInfoLookup"),
2746                                 &desktop_app_info_lookup_info, 0);
2747
2748       g_type_interface_add_prerequisite (g_define_type_id, G_TYPE_OBJECT);
2749
2750       g_once_init_leave (&g_define_type_id__volatile, g_define_type_id);
2751     }
2752
2753   return g_define_type_id__volatile;
2754 }
2755
2756 static void
2757 g_desktop_app_info_lookup_class_init (gpointer g_class,
2758                                       gpointer class_data)
2759 {
2760 }
2761
2762 static void
2763 g_desktop_app_info_lookup_base_init (gpointer g_class)
2764 {
2765 }
2766
2767 /**
2768  * g_desktop_app_info_lookup_get_default_for_uri_scheme:
2769  * @lookup: a #GDesktopAppInfoLookup
2770  * @uri_scheme: a string containing a URI scheme.
2771  *
2772  * Gets the default application for launching applications 
2773  * using this URI scheme for a particular GDesktopAppInfoLookup
2774  * implementation.
2775  *
2776  * The GDesktopAppInfoLookup interface and this function is used
2777  * to implement g_app_info_get_default_for_uri_scheme() backends
2778  * in a GIO module. There is no reason for applications to use it
2779  * directly. Applications should use g_app_info_get_default_for_uri_scheme().
2780  * 
2781  * Returns: #GAppInfo for given @uri_scheme or %NULL on error.
2782  */
2783 GAppInfo *
2784 g_desktop_app_info_lookup_get_default_for_uri_scheme (GDesktopAppInfoLookup *lookup,
2785                                                       const char            *uri_scheme)
2786 {
2787   GDesktopAppInfoLookupIface *iface;
2788   
2789   g_return_val_if_fail (G_IS_DESKTOP_APP_INFO_LOOKUP (lookup), NULL);
2790
2791   iface = G_DESKTOP_APP_INFO_LOOKUP_GET_IFACE (lookup);
2792
2793   return (* iface->get_default_for_uri_scheme) (lookup, uri_scheme);
2794 }
2795
2796 #define __G_DESKTOP_APP_INFO_C__
2797 #include "gioaliasdef.c"