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