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 }