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 }