evolution-3.6.4/smclient/eggdesktopfile.c

No issues found

   1 /* eggdesktopfile.c - Freedesktop.Org Desktop Files
   2  * Copyright (C) 2007 Novell, Inc.
   3  *
   4  * Based on gnome-desktop-item.c
   5  * Copyright (C) 1999, 2000 Red Hat Inc.
   6  * Copyright (C) 2001 George Lebl
   7  *
   8  * This library is free software; you can redistribute it and/or
   9  * modify it under the terms of the GNU Lesser General Public License
  10  * as published by the Free Software Foundation; either version 2 of
  11  * the License, or (at your option) any later version.
  12  *
  13  * This library is distributed in the hope that it will be useful, but
  14  * WITHOUT ANY WARRANTY; without even the implied warranty of
  15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  16  * Lesser General Public License for more details.
  17  *
  18  * You should have received a copy of the GNU Lesser General Public
  19  * License along with this library; see the file COPYING.LIB. If not,
  20  * write to the Free Software Foundation, Inc., 59 Temple Place -
  21  * Suite 330, Boston, MA 02111-1307, USA.
  22  */
  23 
  24 #ifdef HAVE_CONFIG_H
  25 #include <config.h>
  26 #endif
  27 
  28 #include "eggdesktopfile.h"
  29 
  30 #include <string.h>
  31 #include <unistd.h>
  32 
  33 #include <glib/gi18n.h>
  34 #include <gdk/gdkx.h>
  35 #include <gtk/gtk.h>
  36 
  37 struct EggDesktopFile {
  38   GKeyFile           *key_file;
  39   gchar               *source;
  40 
  41   gchar               *name, *icon;
  42   EggDesktopFileType  type;
  43   gchar                document_code;
  44 };
  45 
  46 /**
  47  * egg_desktop_file_new:
  48  * @desktop_file_path: path to a Freedesktop-style Desktop file
  49  * @error: error pointer
  50  *
  51  * Creates a new #EggDesktopFile for @desktop_file.
  52  *
  53  * Return value: the new #EggDesktopFile, or %NULL on error.
  54  **/
  55 EggDesktopFile *
  56 egg_desktop_file_new (const gchar *desktop_file_path,
  57                       GError **error)
  58 {
  59   GKeyFile *key_file;
  60 
  61   key_file = g_key_file_new ();
  62   if (!g_key_file_load_from_file (key_file, desktop_file_path, 0, error))
  63     {
  64       g_key_file_free (key_file);
  65       return NULL;
  66     }
  67 
  68   return egg_desktop_file_new_from_key_file (key_file, desktop_file_path,
  69 					     error);
  70 }
  71 
  72 /**
  73  * egg_desktop_file_new_from_data_dirs:
  74  * @desktop_file_path: relative path to a Freedesktop-style Desktop file
  75  * @error: error pointer
  76  *
  77  * Looks for @desktop_file_path in the paths returned from
  78  * g_get_user_data_dir() and g_get_system_data_dirs(), and creates
  79  * a new #EggDesktopFile from it.
  80  *
  81  * Return value: the new #EggDesktopFile, or %NULL on error.
  82  **/
  83 EggDesktopFile *
  84 egg_desktop_file_new_from_data_dirs (const gchar *desktop_file_path,
  85                                      GError **error)
  86 {
  87   EggDesktopFile *desktop_file;
  88   GKeyFile *key_file;
  89   gchar *full_path;
  90 
  91   key_file = g_key_file_new ();
  92   if (!g_key_file_load_from_data_dirs (key_file, desktop_file_path,
  93 				       &full_path, 0, error))
  94     {
  95       g_key_file_free (key_file);
  96       return NULL;
  97     }
  98 
  99   desktop_file = egg_desktop_file_new_from_key_file (key_file,
 100 						     full_path,
 101 						     error);
 102   g_free (full_path);
 103   return desktop_file;
 104 }
 105 
 106 /**
 107  * egg_desktop_file_new_from_dirs:
 108  * @desktop_file_path: relative path to a Freedesktop-style Desktop file
 109  * @search_dirs: NULL-terminated array of directories to search
 110  * @error: error pointer
 111  *
 112  * Looks for @desktop_file_path in the paths returned from
 113  * g_get_user_data_dir() and g_get_system_data_dirs(), and creates
 114  * a new #EggDesktopFile from it.
 115  *
 116  * Return value: the new #EggDesktopFile, or %NULL on error.
 117  **/
 118 EggDesktopFile *
 119 egg_desktop_file_new_from_dirs (const gchar *desktop_file_path,
 120                                 const gchar **search_dirs,
 121                                 GError **error)
 122 {
 123   EggDesktopFile *desktop_file;
 124   GKeyFile *key_file;
 125   gchar *full_path;
 126 
 127   key_file = g_key_file_new ();
 128   if (!g_key_file_load_from_dirs (key_file, desktop_file_path, search_dirs,
 129 				  &full_path, 0, error))
 130     {
 131       g_key_file_free (key_file);
 132       return NULL;
 133     }
 134 
 135   desktop_file = egg_desktop_file_new_from_key_file (key_file,
 136 						     full_path,
 137 						     error);
 138   g_free (full_path);
 139   return desktop_file;
 140 }
 141 
 142 /**
 143  * egg_desktop_file_new_from_key_file:
 144  * @key_file: a #GKeyFile representing a desktop file
 145  * @source: the path or URI that @key_file was loaded from, or %NULL
 146  * @error: error pointer
 147  *
 148  * Creates a new #EggDesktopFile for @key_file. Assumes ownership of
 149  * @key_file (on success or failure); you should consider @key_file to
 150  * be freed after calling this function.
 151  *
 152  * Return value: the new #EggDesktopFile, or %NULL on error.
 153  **/
 154 EggDesktopFile *
 155 egg_desktop_file_new_from_key_file (GKeyFile *key_file,
 156                                     const gchar *source,
 157                                     GError **error)
 158 {
 159   EggDesktopFile *desktop_file;
 160   gchar *version, *type;
 161 
 162   if (!g_key_file_has_group (key_file, EGG_DESKTOP_FILE_GROUP))
 163     {
 164       g_set_error (error, EGG_DESKTOP_FILE_ERROR,
 165 		   EGG_DESKTOP_FILE_ERROR_INVALID,
 166 		   _("File is not a valid .desktop file"));
 167       g_key_file_free (key_file);
 168       return NULL;
 169     }
 170 
 171   version = g_key_file_get_value (key_file, EGG_DESKTOP_FILE_GROUP,
 172 				  EGG_DESKTOP_FILE_KEY_VERSION,
 173 				  NULL);
 174   if (version)
 175     {
 176       gdouble version_num;
 177       gchar *end;
 178 
 179       version_num = g_ascii_strtod (version, &end);
 180       if (*end)
 181 	{
 182 	  g_warning ("Invalid Version string '%s' in %s",
 183 		     version, source ? source : "(unknown)");
 184 	}
 185       else if (version_num > 1.0)
 186 	{
 187 	  g_set_error (error, EGG_DESKTOP_FILE_ERROR,
 188 		       EGG_DESKTOP_FILE_ERROR_INVALID,
 189 		       _("Unrecognized desktop file Version '%s'"), version);
 190 	  g_free (version);
 191 	  g_key_file_free (key_file);
 192 	  return NULL;
 193 	}
 194       g_free (version);
 195     }
 196 
 197   desktop_file = g_new0 (EggDesktopFile, 1);
 198   desktop_file->key_file = key_file;
 199 
 200   if (g_path_is_absolute (source))
 201     desktop_file->source = g_filename_to_uri (source, NULL, NULL);
 202   else
 203     desktop_file->source = g_strdup (source);
 204 
 205   desktop_file->name = g_key_file_get_string (key_file, EGG_DESKTOP_FILE_GROUP,
 206 					      EGG_DESKTOP_FILE_KEY_NAME, error);
 207   if (!desktop_file->name)
 208     {
 209       egg_desktop_file_free (desktop_file);
 210       return NULL;
 211     }
 212 
 213   type = g_key_file_get_string (key_file, EGG_DESKTOP_FILE_GROUP,
 214 				EGG_DESKTOP_FILE_KEY_TYPE, error);
 215   if (!type)
 216     {
 217       egg_desktop_file_free (desktop_file);
 218       return NULL;
 219     }
 220 
 221   if (!strcmp (type, "Application"))
 222     {
 223       gchar *exec, *p;
 224 
 225       desktop_file->type = EGG_DESKTOP_FILE_TYPE_APPLICATION;
 226 
 227       exec = g_key_file_get_string (key_file,
 228 				    EGG_DESKTOP_FILE_GROUP,
 229 				    EGG_DESKTOP_FILE_KEY_EXEC,
 230 				    error);
 231       if (!exec)
 232 	{
 233 	  egg_desktop_file_free (desktop_file);
 234 	  g_free (type);
 235 	  return NULL;
 236 	}
 237 
 238       /* See if it takes paths or URIs or neither */
 239       for (p = exec; *p; p++)
 240 	{
 241 	  if (*p == '%')
 242 	    {
 243 	      if (p[1] == '\0' || strchr ("FfUu", p[1]))
 244 		{
 245 		  desktop_file->document_code = p[1];
 246 		  break;
 247 		}
 248 	      p++;
 249 	    }
 250 	}
 251 
 252       g_free (exec);
 253     }
 254   else if (!strcmp (type, "Link"))
 255     {
 256       gchar *url;
 257 
 258       desktop_file->type = EGG_DESKTOP_FILE_TYPE_LINK;
 259 
 260       url = g_key_file_get_string (key_file,
 261 				   EGG_DESKTOP_FILE_GROUP,
 262 				   EGG_DESKTOP_FILE_KEY_URL,
 263 				   error);
 264       if (!url)
 265 	{
 266 	  egg_desktop_file_free (desktop_file);
 267 	  g_free (type);
 268 	  return NULL;
 269 	}
 270       g_free (url);
 271     }
 272   else if (!strcmp (type, "Directory"))
 273     desktop_file->type = EGG_DESKTOP_FILE_TYPE_DIRECTORY;
 274   else
 275     desktop_file->type = EGG_DESKTOP_FILE_TYPE_UNRECOGNIZED;
 276 
 277   g_free (type);
 278 
 279   /* Check the Icon key */
 280   desktop_file->icon = g_key_file_get_string (key_file,
 281 					      EGG_DESKTOP_FILE_GROUP,
 282 					      EGG_DESKTOP_FILE_KEY_ICON,
 283 					      NULL);
 284   if (desktop_file->icon && !g_path_is_absolute (desktop_file->icon))
 285     {
 286       gchar *ext;
 287 
 288       /* Lots of .desktop files still get this wrong */
 289       ext = strrchr (desktop_file->icon, '.');
 290       if (ext && (!strcmp (ext, ".png") ||
 291 		  !strcmp (ext, ".xpm") ||
 292 		  !strcmp (ext, ".svg")))
 293 	{
 294 	  g_warning ("Desktop file '%s' has malformed Icon key '%s'"
 295 		     "(should not include extension)",
 296 		     source ? source : "(unknown)",
 297 		     desktop_file->icon);
 298 	  *ext = '\0';
 299 	}
 300     }
 301 
 302   return desktop_file;
 303 }
 304 
 305 /**
 306  * egg_desktop_file_free:
 307  * @desktop_file: an #EggDesktopFile
 308  *
 309  * Frees @desktop_file.
 310  **/
 311 void
 312 egg_desktop_file_free (EggDesktopFile *desktop_file)
 313 {
 314   g_key_file_free (desktop_file->key_file);
 315   g_free (desktop_file->source);
 316   g_free (desktop_file->name);
 317   g_free (desktop_file->icon);
 318   g_free (desktop_file);
 319 }
 320 
 321 /**
 322  * egg_desktop_file_get_source:
 323  * @desktop_file: an #EggDesktopFile
 324  *
 325  * Gets the URI that @desktop_file was loaded from.
 326  *
 327  * Return value: @desktop_file's source URI
 328  **/
 329 const gchar *
 330 egg_desktop_file_get_source (EggDesktopFile *desktop_file)
 331 {
 332   return desktop_file->source;
 333 }
 334 
 335 /**
 336  * egg_desktop_file_get_desktop_file_type:
 337  * @desktop_file: an #EggDesktopFile
 338  *
 339  * Gets the desktop file type of @desktop_file.
 340  *
 341  * Return value: @desktop_file's type
 342  **/
 343 EggDesktopFileType
 344 egg_desktop_file_get_desktop_file_type (EggDesktopFile *desktop_file)
 345 {
 346   return desktop_file->type;
 347 }
 348 
 349 /**
 350  * egg_desktop_file_get_name:
 351  * @desktop_file: an #EggDesktopFile
 352  *
 353  * Gets the (localized) value of @desktop_file's "Name" key.
 354  *
 355  * Return value: the application/link name
 356  **/
 357 const gchar *
 358 egg_desktop_file_get_name (EggDesktopFile *desktop_file)
 359 {
 360   return desktop_file->name;
 361 }
 362 
 363 /**
 364  * egg_desktop_file_get_icon:
 365  * @desktop_file: an #EggDesktopFile
 366  *
 367  * Gets the value of @desktop_file's "Icon" key.
 368  *
 369  * If the icon string is a full path (that is, if g_path_is_absolute()
 370  * returns %TRUE when called on it), it points to a file containing an
 371  * unthemed icon. If the icon string is not a full path, it is the
 372  * name of a themed icon, which can be looked up with %GtkIconTheme,
 373  * or passed directly to a theme-aware widget like %GtkImage or
 374  * %GtkCellRendererPixbuf.
 375  *
 376  * Return value: the icon path or name
 377  **/
 378 const gchar *
 379 egg_desktop_file_get_icon (EggDesktopFile *desktop_file)
 380 {
 381   return desktop_file->icon;
 382 }
 383 
 384 gboolean
 385 egg_desktop_file_has_key (EggDesktopFile *desktop_file,
 386                           const gchar *key,
 387                           GError **error)
 388 {
 389   return g_key_file_has_key (desktop_file->key_file,
 390 			     EGG_DESKTOP_FILE_GROUP, key,
 391 			     error);
 392 }
 393 
 394 gchar *
 395 egg_desktop_file_get_string (EggDesktopFile *desktop_file,
 396                              const gchar *key,
 397                              GError **error)
 398 {
 399   return g_key_file_get_string (desktop_file->key_file,
 400 				EGG_DESKTOP_FILE_GROUP, key,
 401 				error);
 402 }
 403 
 404 gchar *
 405 egg_desktop_file_get_locale_string (EggDesktopFile *desktop_file,
 406                                     const gchar *key,
 407                                     const gchar *locale,
 408                                     GError **error)
 409 {
 410   return g_key_file_get_locale_string (desktop_file->key_file,
 411 				       EGG_DESKTOP_FILE_GROUP, key, locale,
 412 				       error);
 413 }
 414 
 415 gboolean
 416 egg_desktop_file_get_boolean (EggDesktopFile *desktop_file,
 417                               const gchar *key,
 418                               GError **error)
 419 {
 420   return g_key_file_get_boolean (desktop_file->key_file,
 421 				 EGG_DESKTOP_FILE_GROUP, key,
 422 				 error);
 423 }
 424 
 425 gdouble
 426 egg_desktop_file_get_numeric (EggDesktopFile *desktop_file,
 427                               const gchar *key,
 428                               GError **error)
 429 {
 430   return g_key_file_get_double (desktop_file->key_file,
 431 				EGG_DESKTOP_FILE_GROUP, key,
 432 				error);
 433 }
 434 
 435 gchar **
 436 egg_desktop_file_get_string_list (EggDesktopFile *desktop_file,
 437                                   const gchar *key,
 438                                   gsize *length,
 439                                   GError **error)
 440 {
 441   return g_key_file_get_string_list (desktop_file->key_file,
 442 				     EGG_DESKTOP_FILE_GROUP, key, length,
 443 				     error);
 444 }
 445 
 446 gchar **
 447 egg_desktop_file_get_locale_string_list (EggDesktopFile *desktop_file,
 448                                          const gchar *key,
 449                                          const gchar *locale,
 450                                          gsize *length,
 451                                          GError **error)
 452 {
 453   return g_key_file_get_locale_string_list (desktop_file->key_file,
 454 					    EGG_DESKTOP_FILE_GROUP, key,
 455 					    locale, length,
 456 					    error);
 457 }
 458 
 459 /**
 460  * egg_desktop_file_can_launch:
 461  * @desktop_file: an #EggDesktopFile
 462  * @desktop_environment: the name of the running desktop environment,
 463  * or %NULL
 464  *
 465  * Tests if @desktop_file can/should be launched in the current
 466  * environment. If @desktop_environment is non-%NULL, @desktop_file's
 467  * "OnlyShowIn" and "NotShowIn" keys are checked to make sure that
 468  * this desktop_file is appropriate for the named environment.
 469  *
 470  * Furthermore, if @desktop_file has type
 471  * %EGG_DESKTOP_FILE_TYPE_APPLICATION, its "TryExec" key (if any) is
 472  * also checked, to make sure the binary it points to exists.
 473  *
 474  * egg_desktop_file_can_launch() does NOT check the value of the
 475  * "Hidden" key.
 476  *
 477  * Return value: %TRUE if @desktop_file can be launched
 478  **/
 479 gboolean
 480 egg_desktop_file_can_launch (EggDesktopFile *desktop_file,
 481                              const gchar *desktop_environment)
 482 {
 483   gchar *try_exec, *found_program;
 484   gchar **only_show_in, **not_show_in;
 485   gboolean found;
 486   gint i;
 487 
 488   if (desktop_file->type != EGG_DESKTOP_FILE_TYPE_APPLICATION &&
 489       desktop_file->type != EGG_DESKTOP_FILE_TYPE_LINK)
 490     return FALSE;
 491 
 492   if (desktop_environment)
 493     {
 494       only_show_in = g_key_file_get_string_list (desktop_file->key_file,
 495 						 EGG_DESKTOP_FILE_GROUP,
 496 						 EGG_DESKTOP_FILE_KEY_ONLY_SHOW_IN,
 497 						 NULL, NULL);
 498       if (only_show_in)
 499 	{
 500 	  for (i = 0, found = FALSE; only_show_in[i] && !found; i++)
 501 	    {
 502 	      if (!strcmp (only_show_in[i], desktop_environment))
 503 		found = TRUE;
 504 	    }
 505 
 506 	  g_strfreev (only_show_in);
 507 
 508 	  if (!found)
 509 	    return FALSE;
 510 	}
 511 
 512       not_show_in = g_key_file_get_string_list (desktop_file->key_file,
 513 						EGG_DESKTOP_FILE_GROUP,
 514 						EGG_DESKTOP_FILE_KEY_NOT_SHOW_IN,
 515 						NULL, NULL);
 516       if (not_show_in)
 517 	{
 518 	  for (i = 0, found = FALSE; not_show_in[i] && !found; i++)
 519 	    {
 520 	      if (!strcmp (not_show_in[i], desktop_environment))
 521 		found = TRUE;
 522 	    }
 523 
 524 	  g_strfreev (not_show_in);
 525 
 526 	  if (found)
 527 	    return FALSE;
 528 	}
 529     }
 530 
 531   if (desktop_file->type == EGG_DESKTOP_FILE_TYPE_APPLICATION)
 532     {
 533       try_exec = g_key_file_get_string (desktop_file->key_file,
 534 					EGG_DESKTOP_FILE_GROUP,
 535 					EGG_DESKTOP_FILE_KEY_TRY_EXEC,
 536 					NULL);
 537       if (try_exec)
 538 	{
 539 	  found_program = g_find_program_in_path (try_exec);
 540 	  g_free (try_exec);
 541 
 542 	  if (!found_program)
 543 	    return FALSE;
 544 	  g_free (found_program);
 545 	}
 546     }
 547 
 548   return TRUE;
 549 }
 550 
 551 /**
 552  * egg_desktop_file_accepts_documents:
 553  * @desktop_file: an #EggDesktopFile
 554  *
 555  * Tests if @desktop_file represents an application that can accept
 556  * documents on the command line.
 557  *
 558  * Return value: %TRUE or %FALSE
 559  **/
 560 gboolean
 561 egg_desktop_file_accepts_documents (EggDesktopFile *desktop_file)
 562 {
 563   return desktop_file->document_code != 0;
 564 }
 565 
 566 /**
 567  * egg_desktop_file_accepts_multiple:
 568  * @desktop_file: an #EggDesktopFile
 569  *
 570  * Tests if @desktop_file can accept multiple documents at once.
 571  *
 572  * If this returns %FALSE, you can still pass multiple documents to
 573  * egg_desktop_file_launch(), but that will result in multiple copies
 574  * of the application being launched. See egg_desktop_file_launch()
 575  * for more details.
 576  *
 577  * Return value: %TRUE or %FALSE
 578  **/
 579 gboolean
 580 egg_desktop_file_accepts_multiple (EggDesktopFile *desktop_file)
 581 {
 582   return (desktop_file->document_code == 'F' ||
 583 	  desktop_file->document_code == 'U');
 584 }
 585 
 586 /**
 587  * egg_desktop_file_accepts_uris:
 588  * @desktop_file: an #EggDesktopFile
 589  *
 590  * Tests if @desktop_file can accept (non-"file:") URIs as documents to
 591  * open.
 592  *
 593  * Return value: %TRUE or %FALSE
 594  **/
 595 gboolean
 596 egg_desktop_file_accepts_uris (EggDesktopFile *desktop_file)
 597 {
 598   return (desktop_file->document_code == 'U' ||
 599 	  desktop_file->document_code == 'u');
 600 }
 601 
 602 static void
 603 append_quoted_word (GString *str,
 604                     const gchar *s,
 605                     gboolean in_single_quotes,
 606                     gboolean in_double_quotes)
 607 {
 608   const gchar *p;
 609 
 610   if (!in_single_quotes && !in_double_quotes)
 611     g_string_append_c (str, '\'');
 612   else if (!in_single_quotes && in_double_quotes)
 613     g_string_append (str, "\"'");
 614 
 615   if (!strchr (s, '\''))
 616     g_string_append (str, s);
 617   else
 618     {
 619       for (p = s; *p != '\0'; p++)
 620 	{
 621 	  if (*p == '\'')
 622 	    g_string_append (str, "'\\''");
 623 	  else
 624 	    g_string_append_c (str, *p);
 625 	}
 626     }
 627 
 628   if (!in_single_quotes && !in_double_quotes)
 629     g_string_append_c (str, '\'');
 630   else if (!in_single_quotes && in_double_quotes)
 631     g_string_append (str, "'\"");
 632 }
 633 
 634 static void
 635 do_percent_subst (EggDesktopFile *desktop_file,
 636                   gchar code,
 637                   GString *str,
 638                   GSList **documents,
 639                   gboolean in_single_quotes,
 640                   gboolean in_double_quotes)
 641 {
 642   GSList *d;
 643   gchar *doc;
 644 
 645   switch (code)
 646     {
 647     case '%':
 648       g_string_append_c (str, '%');
 649       break;
 650 
 651     case 'F':
 652     case 'U':
 653       for (d = *documents; d; d = d->next)
 654 	{
 655 	  doc = d->data;
 656 	  g_string_append (str, " ");
 657 	  append_quoted_word (str, doc, in_single_quotes, in_double_quotes);
 658 	}
 659       *documents = NULL;
 660       break;
 661 
 662     case 'f':
 663     case 'u':
 664       if (*documents)
 665 	{
 666 	  doc = (*documents)->data;
 667 	  g_string_append (str, " ");
 668 	  append_quoted_word (str, doc, in_single_quotes, in_double_quotes);
 669 	  *documents = (*documents)->next;
 670 	}
 671       break;
 672 
 673     case 'i':
 674       if (desktop_file->icon)
 675 	{
 676 	  g_string_append (str, "--icon ");
 677 	  append_quoted_word (str, desktop_file->icon,
 678 			      in_single_quotes, in_double_quotes);
 679 	}
 680       break;
 681 
 682     case 'c':
 683       if (desktop_file->name)
 684 	{
 685 	  append_quoted_word (str, desktop_file->name,
 686 			      in_single_quotes, in_double_quotes);
 687 	}
 688       break;
 689 
 690     case 'k':
 691       if (desktop_file->source)
 692 	{
 693 	  append_quoted_word (str, desktop_file->source,
 694 			      in_single_quotes, in_double_quotes);
 695 	}
 696       break;
 697 
 698     case 'D':
 699     case 'N':
 700     case 'd':
 701     case 'n':
 702     case 'v':
 703     case 'm':
 704       /* Deprecated; skip */
 705       break;
 706 
 707     default:
 708       g_warning ("Unrecognized %%-code '%%%c' in Exec", code);
 709       break;
 710     }
 711 }
 712 
 713 static gchar *
 714 parse_exec (EggDesktopFile *desktop_file,
 715             GSList **documents,
 716             GError **error)
 717 {
 718   gchar *exec, *p, *command;
 719   gboolean escape, single_quot, double_quot;
 720   GString *gs;
 721 
 722   exec = g_key_file_get_string (desktop_file->key_file,
 723 				EGG_DESKTOP_FILE_GROUP,
 724 				EGG_DESKTOP_FILE_KEY_EXEC,
 725 				error);
 726   if (!exec)
 727     return NULL;
 728 
 729   /* Build the command */
 730   gs = g_string_new (NULL);
 731   escape = single_quot = double_quot = FALSE;
 732 
 733   for (p = exec; *p != '\0'; p++)
 734     {
 735       if (escape)
 736 	{
 737 	  escape = FALSE;
 738 	  g_string_append_c (gs, *p);
 739 	}
 740       else if (*p == '\\')
 741 	{
 742 	  if (!single_quot)
 743 	    escape = TRUE;
 744 	  g_string_append_c (gs, *p);
 745 	}
 746       else if (*p == '\'')
 747 	{
 748 	  g_string_append_c (gs, *p);
 749 	  if (!single_quot && !double_quot)
 750 	    single_quot = TRUE;
 751 	  else if (single_quot)
 752 	    single_quot = FALSE;
 753 	}
 754       else if (*p == '"')
 755 	{
 756 	  g_string_append_c (gs, *p);
 757 	  if (!single_quot && !double_quot)
 758 	    double_quot = TRUE;
 759 	  else if (double_quot)
 760 	    double_quot = FALSE;
 761 	}
 762       else if (*p == '%' && p[1])
 763 	{
 764 	  do_percent_subst (desktop_file, p[1], gs, documents,
 765 			    single_quot, double_quot);
 766 	  p++;
 767 	}
 768       else
 769 	g_string_append_c (gs, *p);
 770     }
 771 
 772   g_free (exec);
 773   command = g_string_free (gs, FALSE);
 774 
 775   /* Prepend "xdg-terminal " if needed (FIXME: use gvfs) */
 776   if (g_key_file_has_key (desktop_file->key_file,
 777 			  EGG_DESKTOP_FILE_GROUP,
 778 			  EGG_DESKTOP_FILE_KEY_TERMINAL,
 779 			  NULL))
 780     {
 781       GError *terminal_error = NULL;
 782       gboolean use_terminal =
 783 	g_key_file_get_boolean (desktop_file->key_file,
 784 				EGG_DESKTOP_FILE_GROUP,
 785 				EGG_DESKTOP_FILE_KEY_TERMINAL,
 786 				&terminal_error);
 787       if (terminal_error)
 788 	{
 789 	  g_free (command);
 790 	  g_propagate_error (error, terminal_error);
 791 	  return NULL;
 792 	}
 793 
 794       if (use_terminal)
 795 	{
 796 	  gs = g_string_new ("xdg-terminal ");
 797 	  append_quoted_word (gs, command, FALSE, FALSE);
 798 	  g_free (command);
 799 	  command = g_string_free (gs, FALSE);
 800 	}
 801     }
 802 
 803   return command;
 804 }
 805 
 806 static GSList *
 807 translate_document_list (EggDesktopFile *desktop_file,
 808                          GSList *documents)
 809 {
 810   gboolean accepts_uris = egg_desktop_file_accepts_uris (desktop_file);
 811   GSList *ret, *d;
 812 
 813   for (d = documents, ret = NULL; d; d = d->next)
 814     {
 815       const gchar *document = d->data;
 816       gboolean is_uri = !g_path_is_absolute (document);
 817       gchar *translated;
 818 
 819       if (accepts_uris)
 820 	{
 821 	  if (is_uri)
 822 	    translated = g_strdup (document);
 823 	  else
 824 	    translated = g_filename_to_uri (document, NULL, NULL);
 825 	}
 826       else
 827 	{
 828 	  if (is_uri)
 829 	    translated = g_filename_from_uri (document, NULL, NULL);
 830 	  else
 831 	    translated = g_strdup (document);
 832 	}
 833 
 834       if (translated)
 835 	ret = g_slist_prepend (ret, translated);
 836     }
 837 
 838   return g_slist_reverse (ret);
 839 }
 840 
 841 static void
 842 free_document_list (GSList *documents)
 843 {
 844   GSList *d;
 845 
 846   for (d = documents; d; d = d->next)
 847     g_free (d->data);
 848   g_slist_free (documents);
 849 }
 850 
 851 /**
 852  * egg_desktop_file_parse_exec:
 853  * @desktop_file: a #EggDesktopFile
 854  * @documents: a list of document paths or URIs
 855  * @error: error pointer
 856  *
 857  * Parses @desktop_file's Exec key, inserting @documents into it, and
 858  * returns the result.
 859  *
 860  * If @documents contains non-file: URIs and @desktop_file does not
 861  * accept URIs, those URIs will be ignored. Likewise, if @documents
 862  * contains more elements than @desktop_file accepts, the extra
 863  * documents will be ignored.
 864  *
 865  * Return value: the parsed Exec string
 866  **/
 867 gchar *
 868 egg_desktop_file_parse_exec (EggDesktopFile *desktop_file,
 869                              GSList *documents,
 870                              GError **error)
 871 {
 872   GSList *translated, *docs;
 873   gchar *command;
 874 
 875   docs = translated = translate_document_list (desktop_file, documents);
 876   command = parse_exec (desktop_file, &docs, error);
 877   free_document_list (translated);
 878 
 879   return command;
 880 }
 881 
 882 static gboolean
 883 parse_link (EggDesktopFile *desktop_file,
 884             EggDesktopFile **app_desktop_file,
 885             GSList **documents,
 886             GError **error)
 887 {
 888   gchar *url;
 889   GKeyFile *key_file;
 890 
 891   url = g_key_file_get_string (desktop_file->key_file,
 892 			       EGG_DESKTOP_FILE_GROUP,
 893 			       EGG_DESKTOP_FILE_KEY_URL,
 894 			       error);
 895   if (!url)
 896     return FALSE;
 897   *documents = g_slist_prepend (NULL, url);
 898 
 899   /* FIXME: use gvfs */
 900   key_file = g_key_file_new ();
 901   g_key_file_set_string (key_file, EGG_DESKTOP_FILE_GROUP,
 902 			 EGG_DESKTOP_FILE_KEY_NAME,
 903 			 "xdg-open");
 904   g_key_file_set_string (key_file, EGG_DESKTOP_FILE_GROUP,
 905 			 EGG_DESKTOP_FILE_KEY_TYPE,
 906 			 "Application");
 907   g_key_file_set_string (key_file, EGG_DESKTOP_FILE_GROUP,
 908 			 EGG_DESKTOP_FILE_KEY_EXEC,
 909 			 "xdg-open %u");
 910   *app_desktop_file = egg_desktop_file_new_from_key_file (key_file, NULL, NULL);
 911   return TRUE;
 912 }
 913 
 914 static gchar *
 915 start_startup_notification (GdkDisplay *display,
 916                             EggDesktopFile *desktop_file,
 917                             const gchar *argv0,
 918                             gint screen,
 919                             gint workspace,
 920                             guint32 launch_time)
 921 {
 922   static gint sequence = 0;
 923   gchar *startup_id;
 924   gchar *description, *wmclass;
 925   gchar *screen_str, *workspace_str;
 926 
 927   if (g_key_file_has_key (desktop_file->key_file,
 928 			  EGG_DESKTOP_FILE_GROUP,
 929 			  EGG_DESKTOP_FILE_KEY_STARTUP_NOTIFY,
 930 			  NULL))
 931     {
 932       if (!g_key_file_get_boolean (desktop_file->key_file,
 933 				   EGG_DESKTOP_FILE_GROUP,
 934 				   EGG_DESKTOP_FILE_KEY_STARTUP_NOTIFY,
 935 				   NULL))
 936 	return NULL;
 937       wmclass = NULL;
 938     }
 939   else
 940     {
 941       wmclass = g_key_file_get_string (desktop_file->key_file,
 942 				       EGG_DESKTOP_FILE_GROUP,
 943 				       EGG_DESKTOP_FILE_KEY_STARTUP_WM_CLASS,
 944 				       NULL);
 945       if (!wmclass)
 946 	return NULL;
 947     }
 948 
 949   if (launch_time == (guint32) - 1)
 950     launch_time = gdk_x11_display_get_user_time (display);
 951   startup_id = g_strdup_printf ("%s-%lu-%s-%s-%d_TIME%lu",
 952 				g_get_prgname (),
 953 				(gulong) getpid (),
 954 				g_get_host_name (),
 955 				argv0,
 956 				sequence++,
 957 				(gulong) launch_time);
 958 
 959   description = g_strdup_printf (_("Starting %s"), desktop_file->name);
 960   screen_str = g_strdup_printf ("%d", screen);
 961   workspace_str = workspace == -1 ? NULL : g_strdup_printf ("%d", workspace);
 962 
 963   gdk_x11_display_broadcast_startup_message (display, "new",
 964 					     "ID", startup_id,
 965 					     "NAME", desktop_file->name,
 966 					     "SCREEN", screen_str,
 967 					     "BIN", argv0,
 968 					     "ICON", desktop_file->icon,
 969 					     "DESKTOP", workspace_str,
 970 					     "DESCRIPTION", description,
 971 					     "WMCLASS", wmclass,
 972 					     NULL);
 973 
 974   g_free (description);
 975   g_free (wmclass);
 976   g_free (screen_str);
 977   g_free (workspace_str);
 978 
 979   return startup_id;
 980 }
 981 
 982 static void
 983 end_startup_notification (GdkDisplay *display,
 984                           const gchar *startup_id)
 985 {
 986   gdk_x11_display_broadcast_startup_message (display, "remove",
 987 					     "ID", startup_id,
 988 					     NULL);
 989 }
 990 
 991 #define EGG_DESKTOP_FILE_SN_TIMEOUT_LENGTH (30 /* seconds */)
 992 
 993 typedef struct {
 994   GdkDisplay *display;
 995   gchar *startup_id;
 996 } StartupNotificationData;
 997 
 998 static gboolean
 999 startup_notification_timeout (gpointer data)
1000 {
1001   StartupNotificationData *sn_data = data;
1002 
1003   end_startup_notification (sn_data->display, sn_data->startup_id);
1004   g_object_unref (sn_data->display);
1005   g_free (sn_data->startup_id);
1006   g_free (sn_data);
1007 
1008   return FALSE;
1009 }
1010 
1011 static void
1012 set_startup_notification_timeout (GdkDisplay *display,
1013                                   const gchar *startup_id)
1014 {
1015   StartupNotificationData *sn_data;
1016 
1017   sn_data = g_new (StartupNotificationData, 1);
1018   sn_data->display = g_object_ref (display);
1019   sn_data->startup_id = g_strdup (startup_id);
1020 
1021   g_timeout_add_seconds (EGG_DESKTOP_FILE_SN_TIMEOUT_LENGTH,
1022 			 startup_notification_timeout, sn_data);
1023 }
1024 
1025 static GPtrArray *
1026 array_putenv (GPtrArray *env,
1027               gchar *variable)
1028 {
1029   guint i, keylen;
1030 
1031   if (!env)
1032     {
1033       gchar **envp;
1034 
1035       env = g_ptr_array_new ();
1036 
1037       envp = g_listenv ();
1038       for (i = 0; envp[i]; i++)
1039 	{
1040 	  const gchar *value;
1041 
1042 	  value = g_getenv (envp[i]);
1043 	  g_ptr_array_add (env, g_strdup_printf ("%s=%s", envp[i],
1044 						 value ? value : ""));
1045 	}
1046       g_strfreev (envp);
1047     }
1048 
1049   keylen = strcspn (variable, "=");
1050 
1051   /* Remove old value of key */
1052   for (i = 0; i < env->len; i++)
1053     {
1054       gchar *envvar = env->pdata[i];
1055 
1056       if (!strncmp (envvar, variable, keylen) && envvar[keylen] == '=')
1057 	{
1058 	  g_free (envvar);
1059 	  g_ptr_array_remove_index_fast (env, i);
1060 	  break;
1061 	}
1062     }
1063 
1064   /* Add new value */
1065   g_ptr_array_add (env, g_strdup (variable));
1066 
1067   return env;
1068 }
1069 
1070 static gboolean
1071 egg_desktop_file_launchv (EggDesktopFile *desktop_file,
1072                           GSList *documents,
1073                           va_list args,
1074                           GError **error)
1075 {
1076   EggDesktopFileLaunchOption option;
1077   GSList *translated_documents = NULL, *docs = NULL;
1078   gchar *command, **argv;
1079   gint argc, i, screen_num;
1080   gboolean success, current_success;
1081   GdkDisplay *display;
1082   gchar *startup_id;
1083 
1084   GPtrArray   *env = NULL;
1085   gchar       **variables = NULL;
1086   GdkScreen   *screen = NULL;
1087   gint          workspace = -1;
1088   const gchar  *directory = NULL;
1089   guint32      launch_time = (guint32) - 1;
1090   GSpawnFlags  flags = G_SPAWN_SEARCH_PATH;
1091   GSpawnChildSetupFunc setup_func = NULL;
1092   gpointer     setup_data = NULL;
1093 
1094   GPid        *ret_pid = NULL;
1095   gint         *ret_stdin = NULL, *ret_stdout = NULL, *ret_stderr = NULL;
1096   gchar       **ret_startup_id = NULL;
1097 
1098   if (documents && desktop_file->document_code == 0)
1099     {
1100       g_set_error (error, EGG_DESKTOP_FILE_ERROR,
1101 		   EGG_DESKTOP_FILE_ERROR_NOT_LAUNCHABLE,
1102 		   _("Application does not accept documents on command line"));
1103       return FALSE;
1104     }
1105 
1106   /* Read the options: technically it's incorrect for the caller to
1107    * NULL-terminate the list of options (rather than 0-terminating
1108    * it), but NULL-terminating lets us use G_GNUC_NULL_TERMINATED,
1109    * it's more consistent with other glib/gtk methods, and it will
1110    * work as long as sizeof (gint) <= sizeof (NULL), and NULL is
1111    * represented as 0. (Which is true everywhere we care about.)
1112    */
1113   while ((option = va_arg (args, EggDesktopFileLaunchOption)))
1114     {
1115       switch (option)
1116 	{
1117 	case EGG_DESKTOP_FILE_LAUNCH_CLEARENV:
1118 	  if (env)
1119 	    g_ptr_array_free (env, TRUE);
1120 	  env = g_ptr_array_new ();
1121 	  break;
1122 	case EGG_DESKTOP_FILE_LAUNCH_PUTENV:
1123 	  variables = va_arg (args, gchar **);
1124 	  for (i = 0; variables[i]; i++)
1125 	    env = array_putenv (env, variables[i]);
1126 	  break;
1127 
1128 	case EGG_DESKTOP_FILE_LAUNCH_SCREEN:
1129 	  screen = va_arg (args, GdkScreen *);
1130 	  break;
1131 	case EGG_DESKTOP_FILE_LAUNCH_WORKSPACE:
1132 	  workspace = va_arg (args, gint);
1133 	  break;
1134 
1135 	case EGG_DESKTOP_FILE_LAUNCH_DIRECTORY:
1136 	  directory = va_arg (args, const gchar *);
1137 	  break;
1138 	case EGG_DESKTOP_FILE_LAUNCH_TIME:
1139 	  launch_time = va_arg (args, guint32);
1140 	  break;
1141 	case EGG_DESKTOP_FILE_LAUNCH_FLAGS:
1142 	  flags |= va_arg (args, GSpawnFlags);
1143 	  /* Make sure they didn't set any flags that don't make sense. */
1144 	  flags &= ~G_SPAWN_FILE_AND_ARGV_ZERO;
1145 	  break;
1146 	case EGG_DESKTOP_FILE_LAUNCH_SETUP_FUNC:
1147 	  setup_func = va_arg (args, GSpawnChildSetupFunc);
1148 	  setup_data = va_arg (args, gpointer);
1149 	  break;
1150 
1151 	case EGG_DESKTOP_FILE_LAUNCH_RETURN_PID:
1152 	  ret_pid = va_arg (args, GPid *);
1153 	  break;
1154 	case EGG_DESKTOP_FILE_LAUNCH_RETURN_STDIN_PIPE:
1155 	  ret_stdin = va_arg (args, gint *);
1156 	  break;
1157 	case EGG_DESKTOP_FILE_LAUNCH_RETURN_STDOUT_PIPE:
1158 	  ret_stdout = va_arg (args, gint *);
1159 	  break;
1160 	case EGG_DESKTOP_FILE_LAUNCH_RETURN_STDERR_PIPE:
1161 	  ret_stderr = va_arg (args, gint *);
1162 	  break;
1163 	case EGG_DESKTOP_FILE_LAUNCH_RETURN_STARTUP_ID:
1164 	  ret_startup_id = va_arg (args, gchar **);
1165 	  break;
1166 
1167 	default:
1168 	  g_set_error (error, EGG_DESKTOP_FILE_ERROR,
1169 		       EGG_DESKTOP_FILE_ERROR_UNRECOGNIZED_OPTION,
1170 		       _("Unrecognized launch option: %d"),
1171 		       GPOINTER_TO_INT (option));
1172 	  success = FALSE;
1173 	  goto out;
1174 	}
1175     }
1176 
1177   if (screen)
1178     {
1179       gchar *display_name = gdk_screen_make_display_name (screen);
1180       gchar *display_env = g_strdup_printf ("DISPLAY=%s", display_name);
1181       env = array_putenv (env, display_env);
1182       g_free (display_name);
1183       g_free (display_env);
1184 
1185       display = gdk_screen_get_display (screen);
1186     }
1187   else
1188     {
1189       display = gdk_display_get_default ();
1190       screen = gdk_display_get_default_screen (display);
1191     }
1192   screen_num = gdk_screen_get_number (screen);
1193 
1194   translated_documents = translate_document_list (desktop_file, documents);
1195   docs = translated_documents;
1196 
1197   success = FALSE;
1198 
1199   do
1200     {
1201       command = parse_exec (desktop_file, &docs, error);
1202       if (!command)
1203 	goto out;
1204 
1205       if (!g_shell_parse_argv (command, &argc, &argv, error))
1206 	{
1207 	  g_free (command);
1208 	  goto out;
1209 	}
1210       g_free (command);
1211 
1212       startup_id = start_startup_notification (display, desktop_file,
1213 					       argv[0], screen_num,
1214 					       workspace, launch_time);
1215       if (startup_id)
1216 	{
1217 	  gchar *startup_id_env = g_strdup_printf ("DESKTOP_STARTUP_ID=%s",
1218 						  startup_id);
1219 	  env = array_putenv (env, startup_id_env);
1220 	  g_free (startup_id_env);
1221 	}
1222 
1223       if (env != NULL)
1224 	g_ptr_array_add (env, NULL);
1225 
1226       current_success =
1227 	g_spawn_async_with_pipes (directory,
1228 				  argv,
1229 				  env ? (gchar **)(env->pdata) : NULL,
1230 				  flags,
1231 				  setup_func, setup_data,
1232 				  ret_pid,
1233 				  ret_stdin, ret_stdout, ret_stderr,
1234 				  error);
1235       g_strfreev (argv);
1236 
1237       if (startup_id)
1238 	{
1239 	  if (current_success)
1240 	    {
1241 	      set_startup_notification_timeout (display, startup_id);
1242 
1243 	      if (ret_startup_id)
1244 		*ret_startup_id = startup_id;
1245 	      else
1246 		g_free (startup_id);
1247 	    }
1248 	  else
1249 	    g_free (startup_id);
1250 	}
1251       else if (ret_startup_id)
1252 	*ret_startup_id = NULL;
1253 
1254       if (current_success)
1255 	{
1256 	  /* If we successfully launch any instances of the app, make
1257 	   * sure we return TRUE and don't set @error.
1258 	   */
1259 	  success = TRUE;
1260 	  error = NULL;
1261 
1262 	  /* Also, only set the output params on the first one */
1263 	  ret_pid = NULL;
1264 	  ret_stdin = ret_stdout = ret_stderr = NULL;
1265 	  ret_startup_id = NULL;
1266 	}
1267     }
1268   while (docs && current_success);
1269 
1270  out:
1271   if (env)
1272     {
1273       g_ptr_array_foreach (env, (GFunc) g_free, NULL);
1274       g_ptr_array_free (env, TRUE);
1275     }
1276   free_document_list (translated_documents);
1277 
1278   return success;
1279 }
1280 
1281 /**
1282  * egg_desktop_file_launch:
1283  * @desktop_file: an #EggDesktopFile
1284  * @documents: a list of URIs or paths to documents to open
1285  * @error: error pointer
1286  * @...: additional options
1287  *
1288  * Launches @desktop_file with the given arguments. Additional options
1289  * can be specified as follows:
1290  *
1291  *   %EGG_DESKTOP_FILE_LAUNCH_CLEARENV: (no arguments)
1292  *       clears the environment in the child process
1293  *   %EGG_DESKTOP_FILE_LAUNCH_PUTENV: (gchar **variables)
1294  *       adds the NAME=VALUE strings in the given %NULL-terminated
1295  *       array to the child process's environment
1296  *   %EGG_DESKTOP_FILE_LAUNCH_SCREEN: (GdkScreen *screen)
1297  *       causes the application to be launched on the given screen
1298  *   %EGG_DESKTOP_FILE_LAUNCH_WORKSPACE: (gint workspace)
1299  *       causes the application to be launched on the given workspace
1300  *   %EGG_DESKTOP_FILE_LAUNCH_DIRECTORY: (gchar *dir)
1301  *       causes the application to be launched in the given directory
1302  *   %EGG_DESKTOP_FILE_LAUNCH_TIME: (guint32 launch_time)
1303  *       sets the "launch time" for the application. If the user
1304  *       interacts with another window after @launch_time but before
1305  *       the launched application creates its first window, the window
1306  *       manager may choose to not give focus to the new application.
1307  *       Passing 0 for @launch_time will explicitly request that the
1308  *       application not receive focus.
1309  *   %EGG_DESKTOP_FILE_LAUNCH_FLAGS (GSpawnFlags flags)
1310  *       Sets additional #GSpawnFlags to use. See g_spawn_async() for
1311  *       more details.
1312  *   %EGG_DESKTOP_FILE_LAUNCH_SETUP_FUNC (GSpawnChildSetupFunc, gpointer)
1313  *       Sets the child setup callback and the data to pass to it.
1314  *       (See g_spawn_async() for more details.)
1315  *
1316  *   %EGG_DESKTOP_FILE_LAUNCH_RETURN_PID (GPid **pid)
1317  *       On a successful launch, sets *@pid to the PID of the launched
1318  *       application.
1319  *   %EGG_DESKTOP_FILE_LAUNCH_RETURN_STARTUP_ID (gchar **startup_id)
1320  *       On a successful launch, sets *@startup_id to the Startup
1321  *       Notification "startup id" of the launched application.
1322  *   %EGG_DESKTOP_FILE_LAUNCH_RETURN_STDIN_PIPE (gint *fd)
1323  *       On a successful launch, sets *@fd to the file descriptor of
1324  *       a pipe connected to the application's stdin.
1325  *   %EGG_DESKTOP_FILE_LAUNCH_RETURN_STDOUT_PIPE (gint *fd)
1326  *       On a successful launch, sets *@fd to the file descriptor of
1327  *       a pipe connected to the application's stdout.
1328  *   %EGG_DESKTOP_FILE_LAUNCH_RETURN_STDERR_PIPE (gint *fd)
1329  *       On a successful launch, sets *@fd to the file descriptor of
1330  *       a pipe connected to the application's stderr.
1331  *
1332  * The options should be terminated with a single %NULL.
1333  *
1334  * If @documents contains multiple documents, but
1335  * egg_desktop_file_accepts_multiple() returns %FALSE for
1336  * @desktop_file, then egg_desktop_file_launch() will actually launch
1337  * multiple instances of the application. In that case, the return
1338  * value (as well as any values passed via
1339  * %EGG_DESKTOP_FILE_LAUNCH_RETURN_PID, etc) will only reflect the
1340  * first instance of the application that was launched (but the
1341  * %EGG_DESKTOP_FILE_LAUNCH_SETUP_FUNC will be called for each
1342  * instance).
1343  *
1344  * Return value: %TRUE if the application was successfully launched.
1345  **/
1346 gboolean
1347 egg_desktop_file_launch (EggDesktopFile *desktop_file,
1348                          GSList *documents,
1349                          GError **error,
1350                          ...)
1351 {
1352   va_list args;
1353   gboolean success;
1354   EggDesktopFile *app_desktop_file;
1355 
1356   switch (desktop_file->type)
1357     {
1358     case EGG_DESKTOP_FILE_TYPE_APPLICATION:
1359       va_start (args, error);
1360       success = egg_desktop_file_launchv (desktop_file, documents,
1361 					  args, error);
1362       va_end (args);
1363       break;
1364 
1365     case EGG_DESKTOP_FILE_TYPE_LINK:
1366       if (documents)
1367 	{
1368 	  g_set_error (error, EGG_DESKTOP_FILE_ERROR,
1369 		       EGG_DESKTOP_FILE_ERROR_NOT_LAUNCHABLE,
1370 		       _("Can't pass document URIs to a 'Type=Link' desktop entry"));
1371 	  return FALSE;
1372 	}
1373 
1374       if (!parse_link (desktop_file, &app_desktop_file, &documents, error))
1375 	return FALSE;
1376 
1377       va_start (args, error);
1378       success = egg_desktop_file_launchv (app_desktop_file, documents,
1379 					  args, error);
1380       va_end (args);
1381 
1382       egg_desktop_file_free (app_desktop_file);
1383       free_document_list (documents);
1384       break;
1385 
1386     case EGG_DESKTOP_FILE_TYPE_UNRECOGNIZED:
1387     case EGG_DESKTOP_FILE_TYPE_DIRECTORY:
1388     default:
1389       g_set_error (error, EGG_DESKTOP_FILE_ERROR,
1390 		   EGG_DESKTOP_FILE_ERROR_NOT_LAUNCHABLE,
1391 		   _("Not a launchable item"));
1392       success = FALSE;
1393       break;
1394     }
1395 
1396   return success;
1397 }
1398 
1399 GQuark
1400 egg_desktop_file_error_quark (void)
1401 {
1402   return g_quark_from_static_string ("egg-desktop_file-error-quark");
1403 }
1404 
1405 G_LOCK_DEFINE_STATIC (egg_desktop_file);
1406 static EggDesktopFile *egg_desktop_file;
1407 
1408 static void
1409 egg_set_desktop_file_internal (const gchar *desktop_file_path,
1410                                gboolean set_defaults)
1411 {
1412   GError *error = NULL;
1413 
1414   G_LOCK (egg_desktop_file);
1415   if (egg_desktop_file)
1416     egg_desktop_file_free (egg_desktop_file);
1417 
1418   egg_desktop_file = egg_desktop_file_new (desktop_file_path, &error);
1419   if (error)
1420     {
1421       g_warning ("Could not load desktop file '%s': %s",
1422 		 desktop_file_path, error->message);
1423       g_error_free (error);
1424     }
1425 
1426   if (set_defaults && egg_desktop_file != NULL) {
1427     /* Set localized application name and default window icon */
1428     if (egg_desktop_file->name)
1429       g_set_application_name (egg_desktop_file->name);
1430     if (egg_desktop_file->icon)
1431       {
1432 	if (g_path_is_absolute (egg_desktop_file->icon))
1433 	  gtk_window_set_default_icon_from_file (egg_desktop_file->icon, NULL);
1434 	else
1435 	  gtk_window_set_default_icon_name (egg_desktop_file->icon);
1436       }
1437   }
1438 
1439   G_UNLOCK (egg_desktop_file);
1440 }
1441 
1442 /**
1443  * egg_set_desktop_file:
1444  * @desktop_file_path: path to the application's desktop file
1445  *
1446  * Creates an #EggDesktopFile for the application from the data at
1447  * @desktop_file_path. This will also call g_set_application_name()
1448  * with the localized application name from the desktop file, and
1449  * gtk_window_set_default_icon_name() or
1450  * gtk_window_set_default_icon_from_file() with the application's
1451  * icon. Other code may use additional information from the desktop
1452  * file.
1453  * See egg_set_desktop_file_without_defaults() for a variant of this
1454  * function that does not set the application name and default window
1455  * icon.
1456  *
1457  * Note that for thread safety reasons, this function can only
1458  * be called once, and is mutually exclusive with calling
1459  * egg_set_desktop_file_without_defaults().
1460  **/
1461 void
1462 egg_set_desktop_file (const gchar *desktop_file_path)
1463 {
1464   egg_set_desktop_file_internal (desktop_file_path, TRUE);
1465 }
1466 
1467 /**
1468  * egg_set_desktop_file_without_defaults:
1469  * @desktop_file_path: path to the application's desktop file
1470  *
1471  * Creates an #EggDesktopFile for the application from the data at
1472  * @desktop_file_path.
1473  * See egg_set_desktop_file() for a variant of this function that
1474  * sets the application name and default window icon from the information
1475  * in the desktop file.
1476  *
1477  * Note that for thread safety reasons, this function can only
1478  * be called once, and is mutually exclusive with calling
1479  * egg_set_desktop_file().
1480  **/
1481 void
1482 egg_set_desktop_file_without_defaults (const gchar *desktop_file_path)
1483 {
1484   egg_set_desktop_file_internal (desktop_file_path, FALSE);
1485 }
1486 
1487 /**
1488  * egg_get_desktop_file:
1489  *
1490  * Gets the application's #EggDesktopFile, as set by
1491  * egg_set_desktop_file().
1492  *
1493  * Return value: the #EggDesktopFile, or %NULL if it hasn't been set.
1494  **/
1495 EggDesktopFile *
1496 egg_get_desktop_file (void)
1497 {
1498   EggDesktopFile *retval;
1499 
1500   G_LOCK (egg_desktop_file);
1501   retval = egg_desktop_file;
1502   G_UNLOCK (egg_desktop_file);
1503 
1504   return retval;
1505 }