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