README: add deprecation notice
[nautilus-actions.git] / src / ui / egg-desktop-file.c
blob011ac53c39dcabaef9ff4a88f847c425becbd982
1 /* eggdesktopfile.c - Freedesktop.Org Desktop Files
2 * Copyright (C) 2007 Novell, Inc.
4 * Based on gnome-desktop-item.c
5 * Copyright (C) 1999, 2000 Red Hat Inc.
6 * Copyright (C) 2001 George Lebl
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.
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.
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.
24 #ifdef HAVE_CONFIG_H
25 #include "config.h"
26 #endif
28 #include "egg-desktop-file.h"
30 #include <string.h>
31 #include <unistd.h>
33 #include <glib/gi18n.h>
34 #include <gdk/gdkx.h>
35 #include <gtk/gtk.h>
37 struct EggDesktopFile {
38 GKeyFile *key_file;
39 char *source;
41 char *name, *icon;
42 EggDesktopFileType type;
43 char document_code;
46 /**
47 * egg_desktop_file_new:
48 * @desktop_file_path: path to a Freedesktop-style Desktop file
49 * @error: error pointer
51 * Creates a new #EggDesktopFile for @desktop_file.
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)
58 GKeyFile *key_file;
60 key_file = g_key_file_new ();
61 if (!g_key_file_load_from_file (key_file, desktop_file_path, 0, error))
63 g_key_file_free (key_file);
64 return NULL;
67 return egg_desktop_file_new_from_key_file (key_file, desktop_file_path,
68 error);
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
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.
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)
86 EggDesktopFile *desktop_file;
87 GKeyFile *key_file;
88 char *full_path;
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))
94 g_key_file_free (key_file);
95 return NULL;
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;
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
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.
115 * Return value: the new #EggDesktopFile, or %NULL on error.
117 EggDesktopFile *
118 egg_desktop_file_new_from_dirs (const char *desktop_file_path,
119 const char **search_dirs,
120 GError **error)
122 EggDesktopFile *desktop_file;
123 GKeyFile *key_file;
124 char *full_path;
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))
130 g_key_file_free (key_file);
131 return NULL;
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;
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
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.
151 * Return value: the new #EggDesktopFile, or %NULL on error.
153 EggDesktopFile *
154 egg_desktop_file_new_from_key_file (GKeyFile *key_file,
155 const char *source,
156 GError **error)
158 EggDesktopFile *desktop_file;
159 char *version, *type;
161 if (!g_key_file_has_group (key_file, EGG_DESKTOP_FILE_GROUP))
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;
170 version = g_key_file_get_value (key_file, EGG_DESKTOP_FILE_GROUP,
171 EGG_DESKTOP_FILE_KEY_VERSION,
172 NULL);
173 if (version)
175 double version_num;
176 char *end;
178 version_num = g_ascii_strtod (version, &end);
179 if (*end)
181 g_warning ("Invalid Version string '%s' in %s",
182 version, source ? source : "(unknown)");
184 else if (version_num > 1.0)
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;
193 g_free (version);
196 desktop_file = g_new0 (EggDesktopFile, 1);
197 desktop_file->key_file = key_file;
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);
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)
208 egg_desktop_file_free (desktop_file);
209 return NULL;
212 type = g_key_file_get_string (key_file, EGG_DESKTOP_FILE_GROUP,
213 EGG_DESKTOP_FILE_KEY_TYPE, error);
214 if (!type)
216 egg_desktop_file_free (desktop_file);
217 return NULL;
220 if (!strcmp (type, "Application"))
222 char *exec, *p;
224 desktop_file->type = EGG_DESKTOP_FILE_TYPE_APPLICATION;
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)
232 egg_desktop_file_free (desktop_file);
233 g_free (type);
234 return NULL;
237 /* See if it takes paths or URIs or neither */
238 for (p = exec; *p; p++)
240 if (*p == '%')
242 if (p[1] == '\0' || strchr ("FfUu", p[1]))
244 desktop_file->document_code = p[1];
245 break;
247 p++;
251 g_free (exec);
253 else if (!strcmp (type, "Link"))
255 char *url;
257 desktop_file->type = EGG_DESKTOP_FILE_TYPE_LINK;
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)
265 egg_desktop_file_free (desktop_file);
266 g_free (type);
267 return NULL;
269 g_free (url);
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;
276 g_free (type);
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))
285 char *ext;
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")))
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';
301 return desktop_file;
305 * egg_desktop_file_free:
306 * @desktop_file: an #EggDesktopFile
308 * Frees @desktop_file.
310 void
311 egg_desktop_file_free (EggDesktopFile *desktop_file)
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);
321 * egg_desktop_file_get_source:
322 * @desktop_file: an #EggDesktopFile
324 * Gets the URI that @desktop_file was loaded from.
326 * Return value: @desktop_file's source URI
328 const char *
329 egg_desktop_file_get_source (EggDesktopFile *desktop_file)
331 return desktop_file->source;
335 * egg_desktop_file_get_desktop_file_type:
336 * @desktop_file: an #EggDesktopFile
338 * Gets the desktop file type of @desktop_file.
340 * Return value: @desktop_file's type
342 EggDesktopFileType
343 egg_desktop_file_get_desktop_file_type (EggDesktopFile *desktop_file)
345 return desktop_file->type;
349 * egg_desktop_file_get_name:
350 * @desktop_file: an #EggDesktopFile
352 * Gets the (localized) value of @desktop_file's "Name" key.
354 * Return value: the application/link name
356 const char *
357 egg_desktop_file_get_name (EggDesktopFile *desktop_file)
359 return desktop_file->name;
363 * egg_desktop_file_get_icon:
364 * @desktop_file: an #EggDesktopFile
366 * Gets the value of @desktop_file's "Icon" key.
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.
375 * Return value: the icon path or name
377 const char *
378 egg_desktop_file_get_icon (EggDesktopFile *desktop_file)
380 return desktop_file->icon;
383 gboolean
384 egg_desktop_file_has_key (EggDesktopFile *desktop_file,
385 const char *key,
386 GError **error)
388 return g_key_file_has_key (desktop_file->key_file,
389 EGG_DESKTOP_FILE_GROUP, key,
390 error);
393 char *
394 egg_desktop_file_get_string (EggDesktopFile *desktop_file,
395 const char *key,
396 GError **error)
398 return g_key_file_get_string (desktop_file->key_file,
399 EGG_DESKTOP_FILE_GROUP, key,
400 error);
403 char *
404 egg_desktop_file_get_locale_string (EggDesktopFile *desktop_file,
405 const char *key,
406 const char *locale,
407 GError **error)
409 return g_key_file_get_locale_string (desktop_file->key_file,
410 EGG_DESKTOP_FILE_GROUP, key, locale,
411 error);
414 gboolean
415 egg_desktop_file_get_boolean (EggDesktopFile *desktop_file,
416 const char *key,
417 GError **error)
419 return g_key_file_get_boolean (desktop_file->key_file,
420 EGG_DESKTOP_FILE_GROUP, key,
421 error);
424 double
425 egg_desktop_file_get_numeric (EggDesktopFile *desktop_file,
426 const char *key,
427 GError **error)
429 return g_key_file_get_double (desktop_file->key_file,
430 EGG_DESKTOP_FILE_GROUP, key,
431 error);
434 char **
435 egg_desktop_file_get_string_list (EggDesktopFile *desktop_file,
436 const char *key,
437 gsize *length,
438 GError **error)
440 return g_key_file_get_string_list (desktop_file->key_file,
441 EGG_DESKTOP_FILE_GROUP, key, length,
442 error);
445 char **
446 egg_desktop_file_get_locale_string_list (EggDesktopFile *desktop_file,
447 const char *key,
448 const char *locale,
449 gsize *length,
450 GError **error)
452 return g_key_file_get_locale_string_list (desktop_file->key_file,
453 EGG_DESKTOP_FILE_GROUP, key,
454 locale, length,
455 error);
459 * egg_desktop_file_can_launch:
460 * @desktop_file: an #EggDesktopFile
461 * @desktop_environment: the name of the running desktop environment,
462 * or %NULL
464 * Tests if @desktop_file can/should be launched in the current
465 * environment. If @desktop_environment is non-%NULL, @desktop_file's
466 * "OnlyShowIn" and "NotShowIn" keys are checked to make sure that
467 * this desktop_file is appropriate for the named environment.
469 * Furthermore, if @desktop_file has type
470 * %EGG_DESKTOP_FILE_TYPE_APPLICATION, its "TryExec" key (if any) is
471 * also checked, to make sure the binary it points to exists.
473 * egg_desktop_file_can_launch() does NOT check the value of the
474 * "Hidden" key.
476 * Return value: %TRUE if @desktop_file can be launched
478 gboolean
479 egg_desktop_file_can_launch (EggDesktopFile *desktop_file,
480 const char *desktop_environment)
482 char *try_exec, *found_program;
483 char **only_show_in, **not_show_in;
484 gboolean found;
485 int i;
487 if (desktop_file->type != EGG_DESKTOP_FILE_TYPE_APPLICATION &&
488 desktop_file->type != EGG_DESKTOP_FILE_TYPE_LINK)
489 return FALSE;
491 if (desktop_environment)
493 only_show_in = g_key_file_get_string_list (desktop_file->key_file,
494 EGG_DESKTOP_FILE_GROUP,
495 EGG_DESKTOP_FILE_KEY_ONLY_SHOW_IN,
496 NULL, NULL);
497 if (only_show_in)
499 for (i = 0, found = FALSE; only_show_in[i] && !found; i++)
501 if (!strcmp (only_show_in[i], desktop_environment))
502 found = TRUE;
505 g_strfreev (only_show_in);
507 if (!found)
508 return FALSE;
511 not_show_in = g_key_file_get_string_list (desktop_file->key_file,
512 EGG_DESKTOP_FILE_GROUP,
513 EGG_DESKTOP_FILE_KEY_NOT_SHOW_IN,
514 NULL, NULL);
515 if (not_show_in)
517 for (i = 0, found = FALSE; not_show_in[i] && !found; i++)
519 if (!strcmp (not_show_in[i], desktop_environment))
520 found = TRUE;
523 g_strfreev (not_show_in);
525 if (found)
526 return FALSE;
530 if (desktop_file->type == EGG_DESKTOP_FILE_TYPE_APPLICATION)
532 try_exec = g_key_file_get_string (desktop_file->key_file,
533 EGG_DESKTOP_FILE_GROUP,
534 EGG_DESKTOP_FILE_KEY_TRY_EXEC,
535 NULL);
536 if (try_exec)
538 found_program = g_find_program_in_path (try_exec);
539 g_free (try_exec);
541 if (!found_program)
542 return FALSE;
543 g_free (found_program);
547 return TRUE;
551 * egg_desktop_file_accepts_documents:
552 * @desktop_file: an #EggDesktopFile
554 * Tests if @desktop_file represents an application that can accept
555 * documents on the command line.
557 * Return value: %TRUE or %FALSE
559 gboolean
560 egg_desktop_file_accepts_documents (EggDesktopFile *desktop_file)
562 return desktop_file->document_code != 0;
566 * egg_desktop_file_accepts_multiple:
567 * @desktop_file: an #EggDesktopFile
569 * Tests if @desktop_file can accept multiple documents at once.
571 * If this returns %FALSE, you can still pass multiple documents to
572 * egg_desktop_file_launch(), but that will result in multiple copies
573 * of the application being launched. See egg_desktop_file_launch()
574 * for more details.
576 * Return value: %TRUE or %FALSE
578 gboolean
579 egg_desktop_file_accepts_multiple (EggDesktopFile *desktop_file)
581 return (desktop_file->document_code == 'F' ||
582 desktop_file->document_code == 'U');
586 * egg_desktop_file_accepts_uris:
587 * @desktop_file: an #EggDesktopFile
589 * Tests if @desktop_file can accept (non-"file:") URIs as documents to
590 * open.
592 * Return value: %TRUE or %FALSE
594 gboolean
595 egg_desktop_file_accepts_uris (EggDesktopFile *desktop_file)
597 return (desktop_file->document_code == 'U' ||
598 desktop_file->document_code == 'u');
601 static void
602 append_quoted_word (GString *str,
603 const char *s,
604 gboolean in_single_quotes,
605 gboolean in_double_quotes)
607 const char *p;
609 if (!in_single_quotes && !in_double_quotes)
610 g_string_append_c (str, '\'');
611 else if (!in_single_quotes && in_double_quotes)
612 g_string_append (str, "\"'");
614 if (!strchr (s, '\''))
615 g_string_append (str, s);
616 else
618 for (p = s; *p != '\0'; p++)
620 if (*p == '\'')
621 g_string_append (str, "'\\''");
622 else
623 g_string_append_c (str, *p);
627 if (!in_single_quotes && !in_double_quotes)
628 g_string_append_c (str, '\'');
629 else if (!in_single_quotes && in_double_quotes)
630 g_string_append (str, "'\"");
633 static void
634 do_percent_subst (EggDesktopFile *desktop_file,
635 char code,
636 GString *str,
637 GSList **documents,
638 gboolean in_single_quotes,
639 gboolean in_double_quotes)
641 GSList *d;
642 char *doc;
644 switch (code)
646 case '%':
647 g_string_append_c (str, '%');
648 break;
650 case 'F':
651 case 'U':
652 for (d = *documents; d; d = d->next)
654 doc = d->data;
655 g_string_append (str, " ");
656 append_quoted_word (str, doc, in_single_quotes, in_double_quotes);
658 *documents = NULL;
659 break;
661 case 'f':
662 case 'u':
663 if (*documents)
665 doc = (*documents)->data;
666 g_string_append (str, " ");
667 append_quoted_word (str, doc, in_single_quotes, in_double_quotes);
668 *documents = (*documents)->next;
670 break;
672 case 'i':
673 if (desktop_file->icon)
675 g_string_append (str, "--icon ");
676 append_quoted_word (str, desktop_file->icon,
677 in_single_quotes, in_double_quotes);
679 break;
681 case 'c':
682 if (desktop_file->name)
684 append_quoted_word (str, desktop_file->name,
685 in_single_quotes, in_double_quotes);
687 break;
689 case 'k':
690 if (desktop_file->source)
692 append_quoted_word (str, desktop_file->source,
693 in_single_quotes, in_double_quotes);
695 break;
697 case 'D':
698 case 'N':
699 case 'd':
700 case 'n':
701 case 'v':
702 case 'm':
703 /* Deprecated; skip */
704 break;
706 default:
707 g_warning ("Unrecognized %%-code '%%%c' in Exec", code);
708 break;
712 static char *
713 parse_exec (EggDesktopFile *desktop_file,
714 GSList **documents,
715 GError **error)
717 char *exec, *p, *command;
718 gboolean escape, single_quot, double_quot;
719 GString *gs;
721 exec = g_key_file_get_string (desktop_file->key_file,
722 EGG_DESKTOP_FILE_GROUP,
723 EGG_DESKTOP_FILE_KEY_EXEC,
724 error);
725 if (!exec)
726 return NULL;
728 /* Build the command */
729 gs = g_string_new (NULL);
730 escape = single_quot = double_quot = FALSE;
732 for (p = exec; *p != '\0'; p++)
734 if (escape)
736 escape = FALSE;
737 g_string_append_c (gs, *p);
739 else if (*p == '\\')
741 if (!single_quot)
742 escape = TRUE;
743 g_string_append_c (gs, *p);
745 else if (*p == '\'')
747 g_string_append_c (gs, *p);
748 if (!single_quot && !double_quot)
749 single_quot = TRUE;
750 else if (single_quot)
751 single_quot = FALSE;
753 else if (*p == '"')
755 g_string_append_c (gs, *p);
756 if (!single_quot && !double_quot)
757 double_quot = TRUE;
758 else if (double_quot)
759 double_quot = FALSE;
761 else if (*p == '%' && p[1])
763 do_percent_subst (desktop_file, p[1], gs, documents,
764 single_quot, double_quot);
765 p++;
767 else
768 g_string_append_c (gs, *p);
771 g_free (exec);
772 command = g_string_free (gs, FALSE);
774 /* Prepend "xdg-terminal " if needed (FIXME: use gvfs) */
775 if (g_key_file_has_key (desktop_file->key_file,
776 EGG_DESKTOP_FILE_GROUP,
777 EGG_DESKTOP_FILE_KEY_TERMINAL,
778 NULL))
780 GError *terminal_error = NULL;
781 gboolean use_terminal =
782 g_key_file_get_boolean (desktop_file->key_file,
783 EGG_DESKTOP_FILE_GROUP,
784 EGG_DESKTOP_FILE_KEY_TERMINAL,
785 &terminal_error);
786 if (terminal_error)
788 g_free (command);
789 g_propagate_error (error, terminal_error);
790 return NULL;
793 if (use_terminal)
795 gs = g_string_new ("xdg-terminal ");
796 append_quoted_word (gs, command, FALSE, FALSE);
797 g_free (command);
798 command = g_string_free (gs, FALSE);
802 return command;
805 static GSList *
806 translate_document_list (EggDesktopFile *desktop_file, GSList *documents)
808 gboolean accepts_uris = egg_desktop_file_accepts_uris (desktop_file);
809 GSList *ret, *d;
811 for (d = documents, ret = NULL; d; d = d->next)
813 const char *document = d->data;
814 gboolean is_uri = !g_path_is_absolute (document);
815 char *translated;
817 if (accepts_uris)
819 if (is_uri)
820 translated = g_strdup (document);
821 else
822 translated = g_filename_to_uri (document, NULL, NULL);
824 else
826 if (is_uri)
827 translated = g_filename_from_uri (document, NULL, NULL);
828 else
829 translated = g_strdup (document);
832 if (translated)
833 ret = g_slist_prepend (ret, translated);
836 return g_slist_reverse (ret);
839 static void
840 free_document_list (GSList *documents)
842 GSList *d;
844 for (d = documents; d; d = d->next)
845 g_free (d->data);
846 g_slist_free (documents);
850 * egg_desktop_file_parse_exec:
851 * @desktop_file: a #EggDesktopFile
852 * @documents: a list of document paths or URIs
853 * @error: error pointer
855 * Parses @desktop_file's Exec key, inserting @documents into it, and
856 * returns the result.
858 * If @documents contains non-file: URIs and @desktop_file does not
859 * accept URIs, those URIs will be ignored. Likewise, if @documents
860 * contains more elements than @desktop_file accepts, the extra
861 * documents will be ignored.
863 * Return value: the parsed Exec string
865 char *
866 egg_desktop_file_parse_exec (EggDesktopFile *desktop_file,
867 GSList *documents,
868 GError **error)
870 GSList *translated, *docs;
871 char *command;
873 docs = translated = translate_document_list (desktop_file, documents);
874 command = parse_exec (desktop_file, &docs, error);
875 free_document_list (translated);
877 return command;
880 static gboolean
881 parse_link (EggDesktopFile *desktop_file,
882 EggDesktopFile **app_desktop_file,
883 GSList **documents,
884 GError **error)
886 char *url;
887 GKeyFile *key_file;
889 url = g_key_file_get_string (desktop_file->key_file,
890 EGG_DESKTOP_FILE_GROUP,
891 EGG_DESKTOP_FILE_KEY_URL,
892 error);
893 if (!url)
894 return FALSE;
895 *documents = g_slist_prepend (NULL, url);
897 /* FIXME: use gvfs */
898 key_file = g_key_file_new ();
899 g_key_file_set_string (key_file, EGG_DESKTOP_FILE_GROUP,
900 EGG_DESKTOP_FILE_KEY_NAME,
901 "xdg-open");
902 g_key_file_set_string (key_file, EGG_DESKTOP_FILE_GROUP,
903 EGG_DESKTOP_FILE_KEY_TYPE,
904 "Application");
905 g_key_file_set_string (key_file, EGG_DESKTOP_FILE_GROUP,
906 EGG_DESKTOP_FILE_KEY_EXEC,
907 "xdg-open %u");
908 *app_desktop_file = egg_desktop_file_new_from_key_file (key_file, NULL, NULL);
909 return TRUE;
912 static char *
913 start_startup_notification (GdkDisplay *display,
914 EggDesktopFile *desktop_file,
915 const char *argv0,
916 int screen,
917 int workspace,
918 guint32 launch_time)
920 static int sequence = 0;
921 char *startup_id;
922 char *description, *wmclass;
923 char *screen_str, *workspace_str;
925 if (g_key_file_has_key (desktop_file->key_file,
926 EGG_DESKTOP_FILE_GROUP,
927 EGG_DESKTOP_FILE_KEY_STARTUP_NOTIFY,
928 NULL))
930 if (!g_key_file_get_boolean (desktop_file->key_file,
931 EGG_DESKTOP_FILE_GROUP,
932 EGG_DESKTOP_FILE_KEY_STARTUP_NOTIFY,
933 NULL))
934 return NULL;
935 wmclass = NULL;
937 else
939 wmclass = g_key_file_get_string (desktop_file->key_file,
940 EGG_DESKTOP_FILE_GROUP,
941 EGG_DESKTOP_FILE_KEY_STARTUP_WM_CLASS,
942 NULL);
943 if (!wmclass)
944 return NULL;
947 if (launch_time == (guint32)-1)
948 launch_time = gdk_x11_display_get_user_time (display);
949 startup_id = g_strdup_printf ("%s-%lu-%s-%s-%d_TIME%lu",
950 g_get_prgname (),
951 (unsigned long)getpid (),
952 g_get_host_name (),
953 argv0,
954 sequence++,
955 (unsigned long)launch_time);
957 description = g_strdup_printf (_("Starting %s"), desktop_file->name);
958 screen_str = g_strdup_printf ("%d", screen);
959 workspace_str = workspace == -1 ? NULL : g_strdup_printf ("%d", workspace);
961 gdk_x11_display_broadcast_startup_message (display, "new",
962 "ID", startup_id,
963 "NAME", desktop_file->name,
964 "SCREEN", screen_str,
965 "BIN", argv0,
966 "ICON", desktop_file->icon,
967 "DESKTOP", workspace_str,
968 "DESCRIPTION", description,
969 "WMCLASS", wmclass,
970 NULL);
972 g_free (description);
973 g_free (wmclass);
974 g_free (screen_str);
975 g_free (workspace_str);
977 return startup_id;
980 static void
981 end_startup_notification (GdkDisplay *display,
982 const char *startup_id)
984 gdk_x11_display_broadcast_startup_message (display, "remove",
985 "ID", startup_id,
986 NULL);
989 #define EGG_DESKTOP_FILE_SN_TIMEOUT_LENGTH (30 /* seconds */)
991 typedef struct {
992 GdkDisplay *display;
993 char *startup_id;
994 } StartupNotificationData;
996 static gboolean
997 startup_notification_timeout (gpointer data)
999 StartupNotificationData *sn_data = data;
1001 end_startup_notification (sn_data->display, sn_data->startup_id);
1002 g_object_unref (sn_data->display);
1003 g_free (sn_data->startup_id);
1004 g_free (sn_data);
1006 return FALSE;
1009 static void
1010 set_startup_notification_timeout (GdkDisplay *display,
1011 const char *startup_id)
1013 StartupNotificationData *sn_data;
1015 sn_data = g_new (StartupNotificationData, 1);
1016 sn_data->display = g_object_ref (display);
1017 sn_data->startup_id = g_strdup (startup_id);
1019 g_timeout_add_seconds (EGG_DESKTOP_FILE_SN_TIMEOUT_LENGTH,
1020 startup_notification_timeout, sn_data);
1023 static GPtrArray *
1024 array_putenv (GPtrArray *env, char *variable)
1026 guint i, keylen;
1028 if (!env)
1030 char **envp;
1032 env = g_ptr_array_new ();
1034 envp = g_listenv ();
1035 for (i = 0; envp[i]; i++)
1037 const char *value;
1039 value = g_getenv (envp[i]);
1040 g_ptr_array_add (env, g_strdup_printf ("%s=%s", envp[i],
1041 value ? value : ""));
1043 g_strfreev (envp);
1046 keylen = strcspn (variable, "=");
1048 /* Remove old value of key */
1049 for (i = 0; i < env->len; i++)
1051 char *envvar = env->pdata[i];
1053 if (!strncmp (envvar, variable, keylen) && envvar[keylen] == '=')
1055 g_free (envvar);
1056 g_ptr_array_remove_index_fast (env, i);
1057 break;
1061 /* Add new value */
1062 g_ptr_array_add (env, g_strdup (variable));
1064 return env;
1067 static gboolean
1068 egg_desktop_file_launchv (EggDesktopFile *desktop_file,
1069 GSList *documents, va_list args,
1070 GError **error)
1072 EggDesktopFileLaunchOption option;
1073 GSList *translated_documents = NULL, *docs = NULL;
1074 char *command, **argv;
1075 int argc, i, screen_num;
1076 gboolean success, current_success;
1077 GdkDisplay *display;
1078 char *startup_id;
1080 GPtrArray *env = NULL;
1081 char **variables = NULL;
1082 GdkScreen *screen = NULL;
1083 int workspace = -1;
1084 const char *directory = NULL;
1085 guint32 launch_time = (guint32)-1;
1086 GSpawnFlags flags = G_SPAWN_SEARCH_PATH;
1087 GSpawnChildSetupFunc setup_func = NULL;
1088 gpointer setup_data = NULL;
1090 GPid *ret_pid = NULL;
1091 int *ret_stdin = NULL, *ret_stdout = NULL, *ret_stderr = NULL;
1092 char **ret_startup_id = NULL;
1094 if (documents && desktop_file->document_code == 0)
1096 g_set_error (error, EGG_DESKTOP_FILE_ERROR,
1097 EGG_DESKTOP_FILE_ERROR_NOT_LAUNCHABLE,
1098 _("Application does not accept documents on command line"));
1099 return FALSE;
1102 /* Read the options: technically it's incorrect for the caller to
1103 * NULL-terminate the list of options (rather than 0-terminating
1104 * it), but NULL-terminating lets us use G_GNUC_NULL_TERMINATED,
1105 * it's more consistent with other glib/gtk methods, and it will
1106 * work as long as sizeof (int) <= sizeof (NULL), and NULL is
1107 * represented as 0. (Which is true everywhere we care about.)
1109 while ((option = va_arg (args, EggDesktopFileLaunchOption)))
1111 switch (option)
1113 case EGG_DESKTOP_FILE_LAUNCH_CLEARENV:
1114 if (env)
1115 g_ptr_array_free (env, TRUE);
1116 env = g_ptr_array_new ();
1117 break;
1118 case EGG_DESKTOP_FILE_LAUNCH_PUTENV:
1119 variables = va_arg (args, char **);
1120 for (i = 0; variables[i]; i++)
1121 env = array_putenv (env, variables[i]);
1122 break;
1124 case EGG_DESKTOP_FILE_LAUNCH_SCREEN:
1125 screen = va_arg (args, GdkScreen *);
1126 break;
1127 case EGG_DESKTOP_FILE_LAUNCH_WORKSPACE:
1128 workspace = va_arg (args, int);
1129 break;
1131 case EGG_DESKTOP_FILE_LAUNCH_DIRECTORY:
1132 directory = va_arg (args, const char *);
1133 break;
1134 case EGG_DESKTOP_FILE_LAUNCH_TIME:
1135 launch_time = va_arg (args, guint32);
1136 break;
1137 case EGG_DESKTOP_FILE_LAUNCH_FLAGS:
1138 flags |= va_arg (args, GSpawnFlags);
1139 /* Make sure they didn't set any flags that don't make sense. */
1140 flags &= ~G_SPAWN_FILE_AND_ARGV_ZERO;
1141 break;
1142 case EGG_DESKTOP_FILE_LAUNCH_SETUP_FUNC:
1143 setup_func = va_arg (args, GSpawnChildSetupFunc);
1144 setup_data = va_arg (args, gpointer);
1145 break;
1147 case EGG_DESKTOP_FILE_LAUNCH_RETURN_PID:
1148 ret_pid = va_arg (args, GPid *);
1149 break;
1150 case EGG_DESKTOP_FILE_LAUNCH_RETURN_STDIN_PIPE:
1151 ret_stdin = va_arg (args, int *);
1152 break;
1153 case EGG_DESKTOP_FILE_LAUNCH_RETURN_STDOUT_PIPE:
1154 ret_stdout = va_arg (args, int *);
1155 break;
1156 case EGG_DESKTOP_FILE_LAUNCH_RETURN_STDERR_PIPE:
1157 ret_stderr = va_arg (args, int *);
1158 break;
1159 case EGG_DESKTOP_FILE_LAUNCH_RETURN_STARTUP_ID:
1160 ret_startup_id = va_arg (args, char **);
1161 break;
1163 default:
1164 g_set_error (error, EGG_DESKTOP_FILE_ERROR,
1165 EGG_DESKTOP_FILE_ERROR_UNRECOGNIZED_OPTION,
1166 _("Unrecognized launch option: %d"),
1167 GPOINTER_TO_INT (option));
1168 success = FALSE;
1169 goto out;
1173 if (screen)
1175 char *display_name = gdk_screen_make_display_name (screen);
1176 char *display_env = g_strdup_printf ("DISPLAY=%s", display_name);
1177 env = array_putenv (env, display_env);
1178 g_free (display_name);
1179 g_free (display_env);
1181 display = gdk_screen_get_display (screen);
1183 else
1185 display = gdk_display_get_default ();
1186 screen = gdk_display_get_default_screen (display);
1188 screen_num = gdk_screen_get_number (screen);
1190 translated_documents = translate_document_list (desktop_file, documents);
1191 docs = translated_documents;
1193 success = FALSE;
1197 command = parse_exec (desktop_file, &docs, error);
1198 if (!command)
1199 goto out;
1201 if (!g_shell_parse_argv (command, &argc, &argv, error))
1203 g_free (command);
1204 goto out;
1206 g_free (command);
1208 startup_id = start_startup_notification (display, desktop_file,
1209 argv[0], screen_num,
1210 workspace, launch_time);
1211 if (startup_id)
1213 char *startup_id_env = g_strdup_printf ("DESKTOP_STARTUP_ID=%s",
1214 startup_id);
1215 env = array_putenv (env, startup_id_env);
1216 g_free (startup_id_env);
1219 if (env != NULL)
1220 g_ptr_array_add (env, NULL);
1222 current_success =
1223 g_spawn_async_with_pipes (directory,
1224 argv,
1225 env ? (char **)(env->pdata) : NULL,
1226 flags,
1227 setup_func, setup_data,
1228 ret_pid,
1229 ret_stdin, ret_stdout, ret_stderr,
1230 error);
1231 g_strfreev (argv);
1233 if (startup_id)
1235 if (current_success)
1237 set_startup_notification_timeout (display, startup_id);
1239 if (ret_startup_id)
1240 *ret_startup_id = startup_id;
1241 else
1242 g_free (startup_id);
1244 else
1245 g_free (startup_id);
1247 else if (ret_startup_id)
1248 *ret_startup_id = NULL;
1250 if (current_success)
1252 /* If we successfully launch any instances of the app, make
1253 * sure we return TRUE and don't set @error.
1255 success = TRUE;
1256 error = NULL;
1258 /* Also, only set the output params on the first one */
1259 ret_pid = NULL;
1260 ret_stdin = ret_stdout = ret_stderr = NULL;
1261 ret_startup_id = NULL;
1264 while (docs && current_success);
1266 out:
1267 if (env)
1269 g_ptr_array_foreach (env, (GFunc)g_free, NULL);
1270 g_ptr_array_free (env, TRUE);
1272 free_document_list (translated_documents);
1274 return success;
1278 * egg_desktop_file_launch:
1279 * @desktop_file: an #EggDesktopFile
1280 * @documents: a list of URIs or paths to documents to open
1281 * @error: error pointer
1282 * @...: additional options
1284 * Launches @desktop_file with the given arguments. Additional options
1285 * can be specified as follows:
1287 * %EGG_DESKTOP_FILE_LAUNCH_CLEARENV: (no arguments)
1288 * clears the environment in the child process
1289 * %EGG_DESKTOP_FILE_LAUNCH_PUTENV: (char **variables)
1290 * adds the NAME=VALUE strings in the given %NULL-terminated
1291 * array to the child process's environment
1292 * %EGG_DESKTOP_FILE_LAUNCH_SCREEN: (GdkScreen *screen)
1293 * causes the application to be launched on the given screen
1294 * %EGG_DESKTOP_FILE_LAUNCH_WORKSPACE: (int workspace)
1295 * causes the application to be launched on the given workspace
1296 * %EGG_DESKTOP_FILE_LAUNCH_DIRECTORY: (char *dir)
1297 * causes the application to be launched in the given directory
1298 * %EGG_DESKTOP_FILE_LAUNCH_TIME: (guint32 launch_time)
1299 * sets the "launch time" for the application. If the user
1300 * interacts with another window after @launch_time but before
1301 * the launched application creates its first window, the window
1302 * manager may choose to not give focus to the new application.
1303 * Passing 0 for @launch_time will explicitly request that the
1304 * application not receive focus.
1305 * %EGG_DESKTOP_FILE_LAUNCH_FLAGS (GSpawnFlags flags)
1306 * Sets additional #GSpawnFlags to use. See g_spawn_async() for
1307 * more details.
1308 * %EGG_DESKTOP_FILE_LAUNCH_SETUP_FUNC (GSpawnChildSetupFunc, gpointer)
1309 * Sets the child setup callback and the data to pass to it.
1310 * (See g_spawn_async() for more details.)
1312 * %EGG_DESKTOP_FILE_LAUNCH_RETURN_PID (GPid **pid)
1313 * On a successful launch, sets *@pid to the PID of the launched
1314 * application.
1315 * %EGG_DESKTOP_FILE_LAUNCH_RETURN_STARTUP_ID (char **startup_id)
1316 * On a successful launch, sets *@startup_id to the Startup
1317 * Notification "startup id" of the launched application.
1318 * %EGG_DESKTOP_FILE_LAUNCH_RETURN_STDIN_PIPE (int *fd)
1319 * On a successful launch, sets *@fd to the file descriptor of
1320 * a pipe connected to the application's stdin.
1321 * %EGG_DESKTOP_FILE_LAUNCH_RETURN_STDOUT_PIPE (int *fd)
1322 * On a successful launch, sets *@fd to the file descriptor of
1323 * a pipe connected to the application's stdout.
1324 * %EGG_DESKTOP_FILE_LAUNCH_RETURN_STDERR_PIPE (int *fd)
1325 * On a successful launch, sets *@fd to the file descriptor of
1326 * a pipe connected to the application's stderr.
1328 * The options should be terminated with a single %NULL.
1330 * If @documents contains multiple documents, but
1331 * egg_desktop_file_accepts_multiple() returns %FALSE for
1332 * @desktop_file, then egg_desktop_file_launch() will actually launch
1333 * multiple instances of the application. In that case, the return
1334 * value (as well as any values passed via
1335 * %EGG_DESKTOP_FILE_LAUNCH_RETURN_PID, etc) will only reflect the
1336 * first instance of the application that was launched (but the
1337 * %EGG_DESKTOP_FILE_LAUNCH_SETUP_FUNC will be called for each
1338 * instance).
1340 * Return value: %TRUE if the application was successfully launched.
1342 gboolean
1343 egg_desktop_file_launch (EggDesktopFile *desktop_file,
1344 GSList *documents, GError **error,
1345 ...)
1347 va_list args;
1348 gboolean success;
1349 EggDesktopFile *app_desktop_file;
1351 switch (desktop_file->type)
1353 case EGG_DESKTOP_FILE_TYPE_APPLICATION:
1354 va_start (args, error);
1355 success = egg_desktop_file_launchv (desktop_file, documents,
1356 args, error);
1357 va_end (args);
1358 break;
1360 case EGG_DESKTOP_FILE_TYPE_LINK:
1361 if (documents)
1363 g_set_error (error, EGG_DESKTOP_FILE_ERROR,
1364 EGG_DESKTOP_FILE_ERROR_NOT_LAUNCHABLE,
1365 _("Can't pass document URIs to a 'Type=Link' desktop entry"));
1366 return FALSE;
1369 if (!parse_link (desktop_file, &app_desktop_file, &documents, error))
1370 return FALSE;
1372 va_start (args, error);
1373 success = egg_desktop_file_launchv (app_desktop_file, documents,
1374 args, error);
1375 va_end (args);
1377 egg_desktop_file_free (app_desktop_file);
1378 free_document_list (documents);
1379 break;
1381 case EGG_DESKTOP_FILE_TYPE_UNRECOGNIZED:
1382 case EGG_DESKTOP_FILE_TYPE_DIRECTORY:
1383 default:
1384 g_set_error (error, EGG_DESKTOP_FILE_ERROR,
1385 EGG_DESKTOP_FILE_ERROR_NOT_LAUNCHABLE,
1386 _("Not a launchable item"));
1387 success = FALSE;
1388 break;
1391 return success;
1395 GQuark
1396 egg_desktop_file_error_quark (void)
1398 return g_quark_from_static_string ("egg-desktop_file-error-quark");
1402 G_LOCK_DEFINE_STATIC (egg_desktop_file);
1403 static EggDesktopFile *egg_desktop_file;
1405 static void
1406 egg_set_desktop_file_internal (const char *desktop_file_path,
1407 gboolean set_defaults)
1409 GError *error = NULL;
1411 G_LOCK (egg_desktop_file);
1412 if (egg_desktop_file)
1413 egg_desktop_file_free (egg_desktop_file);
1415 egg_desktop_file = egg_desktop_file_new (desktop_file_path, &error);
1416 if (error)
1418 g_warning ("Could not load desktop file '%s': %s",
1419 desktop_file_path, error->message);
1420 g_error_free (error);
1423 if (set_defaults && egg_desktop_file != NULL) {
1424 /* Set localized application name and default window icon */
1425 if (egg_desktop_file->name)
1426 g_set_application_name (egg_desktop_file->name);
1427 if (egg_desktop_file->icon)
1429 if (g_path_is_absolute (egg_desktop_file->icon))
1430 gtk_window_set_default_icon_from_file (egg_desktop_file->icon, NULL);
1431 else
1432 gtk_window_set_default_icon_name (egg_desktop_file->icon);
1436 G_UNLOCK (egg_desktop_file);
1440 * egg_set_desktop_file:
1441 * @desktop_file_path: path to the application's desktop file
1443 * Creates an #EggDesktopFile for the application from the data at
1444 * @desktop_file_path. This will also call g_set_application_name()
1445 * with the localized application name from the desktop file, and
1446 * gtk_window_set_default_icon_name() or
1447 * gtk_window_set_default_icon_from_file() with the application's
1448 * icon. Other code may use additional information from the desktop
1449 * file.
1450 * See egg_set_desktop_file_without_defaults() for a variant of this
1451 * function that does not set the application name and default window
1452 * icon.
1454 * Note that for thread safety reasons, this function can only
1455 * be called once, and is mutually exclusive with calling
1456 * egg_set_desktop_file_without_defaults().
1458 void
1459 egg_set_desktop_file (const char *desktop_file_path)
1461 egg_set_desktop_file_internal (desktop_file_path, TRUE);
1465 * egg_set_desktop_file_without_defaults:
1466 * @desktop_file_path: path to the application's desktop file
1468 * Creates an #EggDesktopFile for the application from the data at
1469 * @desktop_file_path.
1470 * See egg_set_desktop_file() for a variant of this function that
1471 * sets the application name and default window icon from the information
1472 * in the desktop file.
1474 * Note that for thread safety reasons, this function can only
1475 * be called once, and is mutually exclusive with calling
1476 * egg_set_desktop_file().
1478 void
1479 egg_set_desktop_file_without_defaults (const char *desktop_file_path)
1481 egg_set_desktop_file_internal (desktop_file_path, FALSE);
1485 * egg_get_desktop_file:
1487 * Gets the application's #EggDesktopFile, as set by
1488 * egg_set_desktop_file().
1490 * Return value: the #EggDesktopFile, or %NULL if it hasn't been set.
1492 EggDesktopFile *
1493 egg_get_desktop_file (void)
1495 EggDesktopFile *retval;
1497 G_LOCK (egg_desktop_file);
1498 retval = egg_desktop_file;
1499 G_UNLOCK (egg_desktop_file);
1501 return retval;