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