1 /* vim: set sw=4 ts=4 sts=4 et: */
10 #include <sys/types.h>
14 # include <winsock2.h>
18 #include <Ecore_Str.h>
19 #include <Ecore_File.h>
22 #include "efreet_private.h"
24 #define DESKTOP_VERSION 1.0
27 * The current desktop environment (e.g. "Enlightenment" or "Gnome")
29 static const char *desktop_environment = NULL;
32 * A cache of all loaded desktops, hashed by file name.
33 * Values are Efreet_Desktop structures
35 static Eina_Hash *efreet_desktop_cache = NULL;
38 * A list of the desktop types available
40 static Eina_List *efreet_desktop_types = NULL;
43 * A unique id for each tmp file created while building a command
45 static int efreet_desktop_command_file_id = 0;
48 static int cache_flush = 0;
50 EAPI int EFREET_DESKTOP_TYPE_APPLICATION = 0;
51 EAPI int EFREET_DESKTOP_TYPE_LINK = 0;
52 EAPI int EFREET_DESKTOP_TYPE_DIRECTORY = 0;
56 * Information about custom types
58 typedef struct Efreet_Desktop_Type_Info Efreet_Desktop_Type_Info;
59 struct Efreet_Desktop_Type_Info
63 Efreet_Desktop_Type_Parse_Cb parse_func;
64 Efreet_Desktop_Type_Save_Cb save_func;
65 Efreet_Desktop_Type_Free_Cb free_func;
68 static int efreet_desktop_read(Efreet_Desktop *desktop);
69 static void efreet_desktop_clear(Efreet_Desktop *desktop);
70 static Efreet_Desktop_Type_Info *efreet_desktop_type_parse(const char *type_str);
71 static void *efreet_desktop_application_fields_parse(Efreet_Desktop *desktop,
73 static void efreet_desktop_application_fields_save(Efreet_Desktop *desktop,
75 static void *efreet_desktop_link_fields_parse(Efreet_Desktop *desktop,
77 static void efreet_desktop_link_fields_save(Efreet_Desktop *desktop,
79 static int efreet_desktop_generic_fields_parse(Efreet_Desktop *desktop,
81 static void efreet_desktop_generic_fields_save(Efreet_Desktop *desktop,
83 static Eina_Bool efreet_desktop_x_fields_parse(const Eina_Hash *hash,
87 static Eina_Bool efreet_desktop_x_fields_save(const Eina_Hash *hash,
91 static int efreet_desktop_environment_check(Efreet_Ini *ini);
92 static char *efreet_string_append(char *dest, int *size,
93 int *len, const char *src);
94 static char *efreet_string_append_char(char *dest, int *size,
96 static Eina_List *efreet_desktop_command_build(Efreet_Desktop_Command *command);
97 static void efreet_desktop_command_free(Efreet_Desktop_Command *command);
98 static char *efreet_desktop_command_append_quoted(char *dest, int *size,
100 static char *efreet_desktop_command_append_icon(char *dest, int *size, int *len,
101 Efreet_Desktop *desktop);
102 static char *efreet_desktop_command_append_single(char *dest, int *size, int *len,
103 Efreet_Desktop_Command_File *file,
105 static char *efreet_desktop_command_append_multiple(char *dest, int *size, int *len,
106 Efreet_Desktop_Command *command,
109 static char *efreet_desktop_command_path_absolute(const char *path);
110 static Efreet_Desktop_Command_File *efreet_desktop_command_file_process(
111 Efreet_Desktop_Command *command,
113 static const char *efreet_desktop_command_file_uri_process(const char *uri);
114 static void efreet_desktop_command_file_free(Efreet_Desktop_Command_File *file);
116 static void efreet_desktop_cb_download_complete(void *data, const char *file,
118 static int efreet_desktop_cb_download_progress(void *data, const char *file,
119 long int dltotal, long int dlnow,
120 long int ultotal, long int ulnow);
123 static void *efreet_desktop_exec_cb(void *data, Efreet_Desktop *desktop,
124 char *exec, int remaining);
126 static void efreet_desktop_type_info_free(Efreet_Desktop_Type_Info *info);
127 static int efreet_desktop_command_flags_get(Efreet_Desktop *desktop);
128 static void *efreet_desktop_command_execs_process(Efreet_Desktop_Command *command, Eina_List *execs);
132 * @return Returns > 0 on success or 0 on failure
133 * @brief Initialize the Desktop parser subsystem
136 efreet_desktop_init(void)
138 if (init++) return init;
139 if (!eina_stringshare_init()) return --init;
140 if (!ecore_file_init()) return --init;
142 efreet_desktop_cache = eina_hash_string_superfast_new(NULL);
143 efreet_desktop_types = NULL;
145 EFREET_DESKTOP_TYPE_APPLICATION = efreet_desktop_type_add("Application",
146 efreet_desktop_application_fields_parse,
147 efreet_desktop_application_fields_save,
149 EFREET_DESKTOP_TYPE_LINK = efreet_desktop_type_add("Link",
150 efreet_desktop_link_fields_parse,
151 efreet_desktop_link_fields_save, NULL);
152 EFREET_DESKTOP_TYPE_DIRECTORY = efreet_desktop_type_add("Directory", NULL,
160 * @returns the number of initializations left for this system
161 * @brief Attempts to shut down the subsystem if nothing else is using it
164 efreet_desktop_shutdown(void)
166 Efreet_Desktop_Type_Info *info;
168 if (--init) return init;
169 ecore_file_shutdown();
170 eina_stringshare_shutdown();
172 IF_RELEASE(desktop_environment);
173 IF_FREE_HASH(efreet_desktop_cache);
174 while (efreet_desktop_types)
176 info = eina_list_data_get(efreet_desktop_types);
177 efreet_desktop_type_info_free(info);
178 efreet_desktop_types = eina_list_remove_list(efreet_desktop_types,
179 efreet_desktop_types);
187 * @param desktop: The desktop to check
188 * @return Returns 1 if the cache is still valid, 0 otherwise
189 * @brief This will check if the desktop cache is still valid.
192 efreet_desktop_cache_check(Efreet_Desktop *desktop)
194 if (!desktop) return 0;
196 /* have we modified this file since we last read it in? */
197 if ((desktop->cache_flush != cache_flush) ||
198 (ecore_file_mod_time(desktop->orig_path) != desktop->load_time))
205 * @param file: The file to get the Efreet_Desktop from
206 * @return Returns a reference to a cached Efreet_Desktop on success, NULL
207 * on failure. This reference should not be freed.
208 * @brief Gets a reference to an Efreet_Desktop structure representing the
209 * contents of @a file or NULL if @a file is not a valid .desktop file.
211 EAPI Efreet_Desktop *
212 efreet_desktop_get(const char *file)
214 Efreet_Desktop *desktop;
216 if (efreet_desktop_cache)
218 desktop = eina_hash_find(efreet_desktop_cache, file);
221 if (efreet_desktop_cache_check(desktop))
227 efreet_desktop_clear(desktop);
228 if (efreet_desktop_read(desktop))
231 desktop->cache_flush = cache_flush;
236 eina_hash_del(efreet_desktop_cache, file, NULL);
240 desktop = efreet_desktop_new(file);
241 if (!desktop) return NULL;
243 eina_hash_add(efreet_desktop_cache, file, desktop);
249 * @param desktop: The Efreet_Desktop to ref
250 * @return Returns the new reference count
253 efreet_desktop_ref(Efreet_Desktop *desktop)
255 if (!desktop) return 0;
261 * @param file: The file to create the Efreet_Desktop from
262 * @return Returns a new empty_Efreet_Desktop on success, NULL on failure
263 * @brief Creates a new empty Efreet_Desktop structure or NULL on failure
265 EAPI Efreet_Desktop *
266 efreet_desktop_empty_new(const char *file)
268 Efreet_Desktop *desktop;
270 desktop = NEW(Efreet_Desktop, 1);
271 if (!desktop) return NULL;
273 desktop->orig_path = strdup(file);
274 desktop->load_time = ecore_file_mod_time(file);
283 * @param file: The file to create the Efreet_Desktop from
284 * @return Returns a new Efreet_Desktop on success, NULL on failure
285 * @brief Creates a new Efreet_Desktop structure initialized from the
286 * contents of @a file or NULL on failure
288 EAPI Efreet_Desktop *
289 efreet_desktop_new(const char *file)
291 Efreet_Desktop *desktop;
293 if (!ecore_file_exists(file)) return NULL;
295 desktop = NEW(Efreet_Desktop, 1);
296 if (!desktop) return NULL;
298 desktop->orig_path = strdup(file);
300 if (!efreet_desktop_read(desktop))
302 efreet_desktop_free(desktop);
307 desktop->cache_flush = cache_flush;
314 * @param desktop: The desktop to fill
315 * @return Returns 1 on success, 0 on failure
316 * @brief initialize an Efreet_Desktop from the contents of @a file
319 efreet_desktop_read(Efreet_Desktop *desktop)
325 ini = efreet_ini_new(desktop->orig_path);
328 efreet_ini_free(ini);
332 ok = efreet_ini_section_set(ini, "Desktop Entry");
333 if (!ok) ok = efreet_ini_section_set(ini, "KDE Desktop Entry");
336 printf("efreet_desktop_new error: no Desktop Entry section\n");
342 Efreet_Desktop_Type_Info *info;
344 info = efreet_desktop_type_parse(efreet_ini_string_get(ini, "Type"));
347 desktop->type = info->id;
348 desktop->version = efreet_ini_double_get(ini, "Version");
350 if (info->parse_func)
351 desktop->type_data = info->parse_func(desktop, ini);
357 if (!error && !efreet_desktop_environment_check(ini)) error = 1;
358 if (!error && !efreet_desktop_generic_fields_parse(desktop, ini)) error = 1;
360 eina_hash_foreach(ini->section, efreet_desktop_x_fields_parse, desktop);
362 efreet_ini_free(ini);
364 desktop->load_time = ecore_file_mod_time(desktop->orig_path);
373 * @param desktop: The Efreet_Desktop to work with
374 * @return Returns no value
375 * @brief Frees the Efreet_Desktop's data
378 efreet_desktop_clear(Efreet_Desktop *desktop)
382 IF_FREE(desktop->name);
383 IF_FREE(desktop->generic_name);
384 IF_FREE(desktop->comment);
385 IF_FREE(desktop->icon);
386 IF_FREE(desktop->url);
388 IF_FREE(desktop->try_exec);
389 IF_FREE(desktop->exec);
390 IF_FREE(desktop->path);
391 IF_FREE(desktop->startup_wm_class);
393 IF_FREE_LIST(desktop->only_show_in, free);
394 IF_FREE_LIST(desktop->not_show_in, free);
395 while (desktop->categories)
397 data = eina_list_data_get(desktop->categories);
398 eina_stringshare_del(data);
399 desktop->categories = eina_list_remove_list(desktop->categories, desktop->categories);
401 while (desktop->mime_types)
403 data = eina_list_data_get(desktop->mime_types);
404 eina_stringshare_del(data);
405 desktop->mime_types = eina_list_remove_list(desktop->mime_types, desktop->mime_types);
408 IF_FREE_HASH(desktop->x);
410 if (desktop->type_data)
412 Efreet_Desktop_Type_Info *info;
413 info = eina_list_nth(efreet_desktop_types, desktop->type);
415 info->free_func(desktop->type_data);
420 * @param desktop: The desktop file to save
421 * @return Returns 1 on success or 0 on failure
422 * @brief Saves any changes made to @a desktop back to the file on the
426 efreet_desktop_save(Efreet_Desktop *desktop)
428 Efreet_Desktop_Type_Info *info;
432 ini = efreet_ini_new(desktop->orig_path);
433 efreet_ini_section_add(ini, "Desktop Entry");
434 efreet_ini_section_set(ini, "Desktop Entry");
436 info = eina_list_nth(efreet_desktop_types, desktop->type);
439 efreet_ini_string_set(ini, "Type", info->type);
440 if (info->save_func) info->save_func(desktop, ini);
449 if (desktop->only_show_in)
451 val = efreet_desktop_string_list_join(desktop->only_show_in);
452 efreet_ini_string_set(ini, "OnlyShowIn", val);
455 if (desktop->not_show_in)
457 val = efreet_desktop_string_list_join(desktop->not_show_in);
458 efreet_ini_string_set(ini, "NotShowIn", val);
461 efreet_desktop_generic_fields_save(desktop, ini);
462 /* When we save the file, it should be updated to the
463 * latest version that we support! */
464 efreet_ini_double_set(ini, "Version", DESKTOP_VERSION);
466 if (!efreet_ini_save(ini, desktop->orig_path)) ok = 0;
469 if (desktop != eina_hash_find(efreet_desktop_cache, desktop->orig_path))
472 eina_hash_del(efreet_desktop_cache, desktop->orig_path, NULL);
473 eina_hash_add(efreet_desktop_cache, desktop->orig_path,
478 efreet_ini_free(ini);
483 * @param desktop: The desktop file to save
484 * @param file: The filename to save as
485 * @return Returns 1 on success or 0 on failure
486 * @brief Saves @a desktop to @a file
489 efreet_desktop_save_as(Efreet_Desktop *desktop, const char *file)
491 if (desktop == eina_hash_find(efreet_desktop_cache, desktop->orig_path))
494 eina_hash_del(efreet_desktop_cache, desktop->orig_path, NULL);
496 FREE(desktop->orig_path);
497 desktop->orig_path = strdup(file);
498 return efreet_desktop_save(desktop);
503 * @param desktop: The Efreet_Desktop to work with
504 * @return Returns no value
505 * @brief Frees the Efreet_Desktop structure and all of it's data
508 efreet_desktop_free(Efreet_Desktop *desktop)
512 if (!desktop) return;
515 if (desktop->ref > 0) return;
517 if (desktop->cached && efreet_desktop_cache)
518 eina_hash_del(efreet_desktop_cache, desktop->orig_path, NULL);
520 IF_FREE(desktop->orig_path);
522 IF_FREE(desktop->name);
523 IF_FREE(desktop->generic_name);
524 IF_FREE(desktop->comment);
525 IF_FREE(desktop->icon);
526 IF_FREE(desktop->url);
528 IF_FREE(desktop->try_exec);
529 IF_FREE(desktop->exec);
530 IF_FREE(desktop->path);
531 IF_FREE(desktop->startup_wm_class);
533 IF_FREE_LIST(desktop->only_show_in, free);
534 IF_FREE_LIST(desktop->not_show_in, free);
536 EINA_LIST_FREE(desktop->categories, str)
537 eina_stringshare_del(str);
538 EINA_LIST_FREE(desktop->mime_types, str)
539 eina_stringshare_del(str);
541 IF_FREE_HASH(desktop->x);
543 if (desktop->type_data)
545 Efreet_Desktop_Type_Info *info;
546 info = eina_list_nth(efreet_desktop_types, desktop->type);
548 info->free_func(desktop->type_data);
555 * @param desktop: The desktop file to work with
556 * @param files: The files to be substituted into the exec line
557 * @param data: The data pointer to pass
558 * @return Returns the Ecore_Exce for @a desktop
559 * @brief Parses the @a desktop exec line and returns an Ecore_Exe.
562 efreet_desktop_exec(Efreet_Desktop *desktop, Eina_List *files, void *data)
564 efreet_desktop_command_get(desktop, files, efreet_desktop_exec_cb, data);
568 efreet_desktop_exec_cb(void *data, Efreet_Desktop *desktop __UNUSED__,
569 char *exec, int remaining __UNUSED__)
572 ecore_exe_run(exec, data);
579 * @param environment: the environment name
580 * @brief sets the global desktop environment name
583 efreet_desktop_environment_set(const char *environment)
585 if (desktop_environment) eina_stringshare_del(desktop_environment);
586 if (environment) desktop_environment = eina_stringshare_add(environment);
587 else desktop_environment = NULL;
591 * @return environment: the environment name
592 * @brief sets the global desktop environment name
595 efreet_desktop_environment_get(void)
597 return desktop_environment;
601 * @param desktop: The desktop to work with
602 * @return Returns the number of categories assigned to this desktop
603 * @brief Retrieves the number of categories the given @a desktop belongs
607 efreet_desktop_category_count_get(Efreet_Desktop *desktop)
609 if (!desktop || !desktop->categories) return 0;
610 return eina_list_count(desktop->categories);
614 * @param desktop: the desktop
615 * @param category: the category name
616 * @brief add a category to a desktop
619 efreet_desktop_category_add(Efreet_Desktop *desktop, const char *category)
621 if (!desktop) return;
623 if (eina_list_search_unsorted(desktop->categories,
624 EINA_COMPARE_CB(strcmp), category)) return;
626 desktop->categories = eina_list_append(desktop->categories,
627 (void *)eina_stringshare_add(category));
631 * @param desktop: the desktop
632 * @param category: the category name
633 * @brief removes a category from a desktop
634 * @return 1 if the desktop had his category listed, 0 otherwise
637 efreet_desktop_category_del(Efreet_Desktop *desktop, const char *category)
641 if (!desktop || !desktop->categories) return 0;
643 if ((found = eina_list_search_unsorted(desktop->categories,
644 EINA_COMPARE_CB(strcmp), category)))
646 eina_stringshare_del(found);
647 desktop->categories = eina_list_remove(desktop->categories, found);
656 * @param type: The type to add to the list of matching types
657 * @param parse_func: a function to parse out custom fields
658 * @param save_func: a function to save data returned from @a parse_func
659 * @param free_func: a function to free data returned from @a parse_func
660 * @return Returns the id of the new type
661 * @brief Adds the given type to the list of types in the system
664 efreet_desktop_type_add(const char *type, Efreet_Desktop_Type_Parse_Cb parse_func,
665 Efreet_Desktop_Type_Save_Cb save_func,
666 Efreet_Desktop_Type_Free_Cb free_func)
669 Efreet_Desktop_Type_Info *info;
671 info = NEW(Efreet_Desktop_Type_Info, 1);
674 id = eina_list_count(efreet_desktop_types);
677 info->type = strdup(type);
678 info->parse_func = parse_func;
679 info->save_func = save_func;
680 info->free_func = free_func;
682 efreet_desktop_types = eina_list_append(efreet_desktop_types, info);
688 * @brief Add an alias for an existing desktop type.
689 * @param from_type the type to alias (e.g. EFREE_DESKTOP_TYPE_APPLICATION)
690 * @param alias the alias
691 * @return the new type id, or -1 if @p from_type was not valid
693 * This allows applications to add non-standard types that behave exactly as standard types.
696 efreet_desktop_type_alias(int from_type, const char *alias)
698 Efreet_Desktop_Type_Info *info;
699 info = eina_list_nth(efreet_desktop_types, from_type);
700 if (!info) return -1;
702 return efreet_desktop_type_add(alias, info->parse_func, info->save_func, info->free_func);
707 * @brief Free an Efreet Desktop_Type_Info struct
710 efreet_desktop_type_info_free(Efreet_Desktop_Type_Info *info)
718 * @brief get type specific data for custom desktop types
719 * @param desktop the desktop
720 * @return type specific data, or NULL if there is none
723 efreet_desktop_type_data_get(Efreet_Desktop *desktop)
725 return desktop->type_data;
730 * @param type_str: the type as a string
731 * @return the parsed type
732 * @brief parse the type string into an Efreet_Desktop_Type
734 static Efreet_Desktop_Type_Info *
735 efreet_desktop_type_parse(const char *type_str)
737 Efreet_Desktop_Type_Info *info;
740 if (!type_str) return NULL;
742 EINA_LIST_FOREACH(efreet_desktop_types, l, info)
744 if (!strcmp(info->type, type_str))
752 * @param string: the raw string list
753 * @return an Eina_List of ecore string's
754 * @brief Parse ';' separate list of strings according to the desktop spec
757 efreet_desktop_string_list_parse(const char *string)
759 Eina_List *list = NULL;
763 if (!string) return NULL;
765 tmp = strdup(string);
768 while ((p = strchr(s, ';')))
770 if (p > tmp && *(p-1) == '\\') continue;
772 list = eina_list_append(list, (void *)eina_stringshare_add(s));
775 /* If this is true, the .desktop file does not follow the standard */
779 printf("[Efreet]: Found a string list without ';' "
780 "at the end: %s\n", string);
782 list = eina_list_append(list, (void *)eina_stringshare_add(s));
791 * @param list: Eina_List with strings
792 * @return a raw string list
793 * @brief Create a ';' separate list of strings according to the desktop spec
796 efreet_desktop_string_list_join(Eina_List *list)
801 size_t size, pos, len;
803 if (!list) return strdup("");
806 string = malloc(size);
809 EINA_LIST_FOREACH(list, l, tmp)
813 if ((len + pos + 1) >= size)
815 size = len + pos + 1024;
816 string = realloc(string, size);
818 strcpy(string + pos, tmp);
820 strcpy(string + pos, ";");
827 * @brief Tell Efreet to flush any cached desktop entries so it reloads on get.
829 * This flags the cache to be invalid, so next time a desktop file is fetched
830 * it will force it to be re-read off disk next time efreet_desktop_get() is
834 efreet_desktop_cache_flush(void)
841 * @param desktop: the Efreet_Desktop to store parsed fields in
842 * @param ini: the Efreet_Ini to parse fields from
844 * @brief Parse application specific desktop fields
847 efreet_desktop_application_fields_parse(Efreet_Desktop *desktop, Efreet_Ini *ini)
851 val = efreet_ini_string_get(ini, "TryExec");
852 if (val) desktop->try_exec = strdup(val);
854 val = efreet_ini_string_get(ini, "Exec");
855 if (val) desktop->exec = strdup(val);
857 val = efreet_ini_string_get(ini, "Path");
858 if (val) desktop->path = strdup(val);
860 val = efreet_ini_string_get(ini, "StartupWMClass");
861 if (val) desktop->startup_wm_class = strdup(val);
863 desktop->categories = efreet_desktop_string_list_parse(
864 efreet_ini_string_get(ini, "Categories"));
865 desktop->mime_types = efreet_desktop_string_list_parse(
866 efreet_ini_string_get(ini, "MimeType"));
868 desktop->terminal = efreet_ini_boolean_get(ini, "Terminal");
869 desktop->startup_notify = efreet_ini_boolean_get(ini, "StartupNotify");
876 * @param desktop: the Efreet_Desktop to save fields from
877 * @param ini: the Efreet_Ini to save fields to
878 * @return Returns no value
879 * @brief Save application specific desktop fields
882 efreet_desktop_application_fields_save(Efreet_Desktop *desktop, Efreet_Ini *ini)
886 if (desktop->try_exec)
887 efreet_ini_string_set(ini, "TryExec", desktop->try_exec);
890 efreet_ini_string_set(ini, "Exec", desktop->exec);
893 efreet_ini_string_set(ini, "Path", desktop->path);
895 if (desktop->startup_wm_class)
896 efreet_ini_string_set(ini, "StartupWMClass", desktop->startup_wm_class);
898 if (desktop->categories)
900 val = efreet_desktop_string_list_join(desktop->categories);
901 efreet_ini_string_set(ini, "Categories", val);
905 if (desktop->mime_types)
907 val = efreet_desktop_string_list_join(desktop->mime_types);
908 efreet_ini_string_set(ini, "MimeType", val);
912 efreet_ini_boolean_set(ini, "Terminal", desktop->terminal);
913 efreet_ini_boolean_set(ini, "StartupNotify", desktop->startup_notify);
918 * @param desktop: the Efreet_Desktop to store parsed fields in
919 * @param ini: the Efreet_Ini to parse fields from
920 * @return Returns no value
921 * @brief Parse link specific desktop fields
924 efreet_desktop_link_fields_parse(Efreet_Desktop *desktop, Efreet_Ini *ini)
928 val = efreet_ini_string_get(ini, "URL");
929 if (val) desktop->url = strdup(val);
935 * @param desktop: the Efreet_Desktop to save fields from
936 * @param ini: the Efreet_Ini to save fields in
937 * @return Returns no value
938 * @brief Save link specific desktop fields
941 efreet_desktop_link_fields_save(Efreet_Desktop *desktop, Efreet_Ini *ini)
943 if (desktop->url) efreet_ini_string_set(ini, "URL", desktop->url);
948 * @param desktop: the Efreet_Desktop to store parsed fields in
949 * @param ini: the Efreet_Ini to parse fields from
950 * @return 1 if parsed succesfully, 0 otherwise
951 * @brief Parse desktop fields that all types can include
954 efreet_desktop_generic_fields_parse(Efreet_Desktop *desktop, Efreet_Ini *ini)
958 val = efreet_ini_localestring_get(ini, "Name");
959 if (val) desktop->name = strdup(val);
962 printf("efreet_desktop_generic_fields_parse error: no Name\n");
966 val = efreet_ini_localestring_get(ini, "GenericName");
967 if (val) desktop->generic_name = strdup(val);
969 val = efreet_ini_localestring_get(ini, "Comment");
970 if (val) desktop->comment = strdup(val);
972 val = efreet_ini_localestring_get(ini, "Icon");
973 if (val) desktop->icon = strdup(val);
975 desktop->no_display = efreet_ini_boolean_get(ini, "NoDisplay");
976 desktop->hidden = efreet_ini_boolean_get(ini, "Hidden");
983 * @param desktop: the Efreet_Desktop to save fields from
984 * @param ini: the Efreet_Ini to save fields to
985 * @return Returns nothing
986 * @brief Save desktop fields that all types can include
989 efreet_desktop_generic_fields_save(Efreet_Desktop *desktop, Efreet_Ini *ini)
995 efreet_ini_localestring_set(ini, "Name", desktop->name);
996 val = efreet_ini_string_get(ini, "Name");
998 efreet_ini_string_set(ini, "Name", desktop->name);
1000 if (desktop->generic_name)
1002 efreet_ini_localestring_set(ini, "GenericName", desktop->generic_name);
1003 val = efreet_ini_string_get(ini, "GenericName");
1005 efreet_ini_string_set(ini, "GenericName", desktop->generic_name);
1007 if (desktop->comment)
1009 efreet_ini_localestring_set(ini, "Comment", desktop->comment);
1010 val = efreet_ini_string_get(ini, "Comment");
1012 efreet_ini_string_set(ini, "Comment", desktop->comment);
1016 efreet_ini_localestring_set(ini, "Icon", desktop->icon);
1017 val = efreet_ini_string_get(ini, "Icon");
1019 efreet_ini_string_set(ini, "Icon", desktop->icon);
1022 efreet_ini_boolean_set(ini, "NoDisplay", desktop->no_display);
1023 efreet_ini_boolean_set(ini, "Hidden", desktop->hidden);
1025 if (desktop->x) eina_hash_foreach(desktop->x, efreet_desktop_x_fields_save,
1031 * @param node: The node to work with
1032 * @param desktop: The desktop file to work with
1033 * @return Returns always true, to be used in eina_hash_foreach()
1034 * @brief Parses out an X- key from @a node and stores in @a desktop
1037 efreet_desktop_x_fields_parse(const Eina_Hash *hash __UNUSED__, const void *key, void *value, void *fdata)
1039 Efreet_Desktop * desktop = fdata;
1041 if (!desktop) return EINA_TRUE;
1042 if (strncmp(key, "X-", 2)) return EINA_TRUE;
1045 desktop->x = eina_hash_string_superfast_new(EINA_FREE_CB(eina_stringshare_del));
1046 eina_hash_del(desktop->x, key, NULL);
1047 eina_hash_add(desktop->x, key, (void *)eina_stringshare_add(value));
1054 * @param node: The node to work with
1055 * @param ini: The ini file to work with
1056 * @return Returns no value
1057 * @brief Stores an X- key from @a node and stores in @a ini
1060 efreet_desktop_x_fields_save(const Eina_Hash *hash __UNUSED__, const void *key, void *value, void *fdata)
1062 Efreet_Ini *ini = fdata;
1063 efreet_ini_string_set(ini, key, value);
1071 * @param ini: The Efreet_Ini to parse values from
1072 * @return 1 if desktop should be included in current environement, 0 otherwise
1073 * @brief Determines if a desktop should be included in the current environment,
1074 * based on the values of the OnlyShowIn and NotShowIn fields
1077 efreet_desktop_environment_check(Efreet_Ini *ini)
1083 if (!desktop_environment)
1086 list = efreet_desktop_string_list_parse(efreet_ini_string_get(ini, "OnlyShowIn"));
1089 EINA_LIST_FREE(list, val)
1091 if (!strcmp(val, desktop_environment))
1093 eina_stringshare_del(val);
1099 list = efreet_desktop_string_list_parse(efreet_ini_string_get(ini, "NotShowIn"));
1100 EINA_LIST_FREE(list, val)
1102 if (!strcmp(val, desktop_environment))
1104 eina_stringshare_del(val);
1112 * @param desktop: the desktop entry
1113 * @param files: an eina list of file names to execute, as either absolute paths,
1114 * relative paths, or uris
1115 * @param func: a callback to call for each prepared command line
1116 * @param data: user data passed to the callback
1117 * @return Returns the return value of @p func on success or NULL on failure
1118 * @brief Get a command to use to execute a desktop entry.
1121 efreet_desktop_command_get(Efreet_Desktop *desktop, Eina_List *files,
1122 Efreet_Desktop_Command_Cb func, void *data)
1124 return efreet_desktop_command_progress_get(desktop, files, func, NULL, data);
1128 * @param desktop: the desktop entry
1129 * @param files an eina list of local files, as absolute paths, local paths, or file:// uris (or NULL to get exec string with no files appended)
1130 * @return Returns an eina list of exec strings
1131 * @brief Get the command to use to execute a desktop entry
1133 * The returned list and each of its elements must be freed.
1136 efreet_desktop_command_local_get(Efreet_Desktop *desktop, Eina_List *files)
1138 Efreet_Desktop_Command *command;
1140 Eina_List *execs, *l;
1142 if (!desktop || !desktop->exec) return NULL;
1144 command = NEW(Efreet_Desktop_Command, 1);
1145 if (!command) return 0;
1147 command->desktop = desktop;
1149 command->flags = efreet_desktop_command_flags_get(desktop);
1150 /* get the required info for each file passed in */
1153 EINA_LIST_FOREACH(files, l, file)
1155 Efreet_Desktop_Command_File *dcf;
1157 dcf = efreet_desktop_command_file_process(command, file);
1161 efreet_desktop_command_file_free(dcf);
1164 command->files = eina_list_append(command->files, dcf);
1168 execs = efreet_desktop_command_build(command);
1169 efreet_desktop_command_free(command);
1176 * @param desktop: the desktop entry
1177 * @param files: an eina list of file names to execute, as either absolute paths,
1178 * relative paths, or uris
1179 * @param cb_command: a callback to call for each prepared command line
1180 * @param cb_progress: a callback to get progress for the downloads
1181 * @param data: user data passed to the callback
1182 * @return Returns 1 on success or 0 on failure
1183 * @brief Get a command to use to execute a desktop entry, and receive progress
1184 * updates for downloading of remote URI's passed in.
1187 efreet_desktop_command_progress_get(Efreet_Desktop *desktop, Eina_List *files,
1188 Efreet_Desktop_Command_Cb cb_command,
1189 Efreet_Desktop_Progress_Cb cb_progress,
1192 Efreet_Desktop_Command *command;
1197 if (!desktop || !cb_command || !desktop->exec) return NULL;
1199 command = NEW(Efreet_Desktop_Command, 1);
1200 if (!command) return NULL;
1202 command->cb_command = cb_command;
1203 command->cb_progress = cb_progress;
1204 command->data = data;
1205 command->desktop = desktop;
1207 command->flags = efreet_desktop_command_flags_get(desktop);
1208 /* get the required info for each file passed in */
1211 EINA_LIST_FOREACH(files, l, file)
1213 Efreet_Desktop_Command_File *dcf;
1215 dcf = efreet_desktop_command_file_process(command, file);
1217 command->files = eina_list_append(command->files, dcf);
1218 command->num_pending += dcf->pending;
1222 if (command->num_pending == 0)
1225 execs = efreet_desktop_command_build(command);
1226 ret = efreet_desktop_command_execs_process(command, execs);
1227 eina_list_free(execs);
1228 efreet_desktop_command_free(command);
1237 * @brief Determine which file related field codes are present in the Exec string of a .desktop
1238 * @params desktop and Efreet Desktop
1239 * @return a bitmask of file field codes present in exec string
1242 efreet_desktop_command_flags_get(Efreet_Desktop *desktop)
1246 /* first, determine which fields are present in the Exec string */
1247 p = strchr(desktop->exec, '%');
1255 flags |= EFREET_DESKTOP_EXEC_FLAG_FULLPATH;
1259 flags |= EFREET_DESKTOP_EXEC_FLAG_URI;
1263 flags |= EFREET_DESKTOP_EXEC_FLAG_DIR;
1267 flags |= EFREET_DESKTOP_EXEC_FLAG_FILE;
1279 /* NON-SPEC!!! this is to work around LOTS of 'broken' .desktop files that
1280 * do not specify %U/%u, %F/F etc. etc. at all. just a command. this is
1281 * unlikely to be fixed in distributions etc. in the long run as gnome/kde
1282 * seem to have workarounds too so no one notices.
1284 if (!flags) flags |= EFREET_DESKTOP_EXEC_FLAG_FULLPATH;
1294 * @brief Call the command callback for each exec in the list
1299 efreet_desktop_command_execs_process(Efreet_Desktop_Command *command, Eina_List *execs)
1306 num = eina_list_count(execs);
1307 EINA_LIST_FOREACH(execs, l, exec)
1309 ret = command->cb_command(command->data, command->desktop, exec, --num);
1316 * @brief Builds the actual exec string from the raw string and a list of
1317 * processed filename information. The callback passed in to
1318 * efreet_desktop_command_get is called for each exec string created.
1320 * @param command: the command to build
1321 * @return a list of executable strings
1324 efreet_desktop_command_build(Efreet_Desktop_Command *command)
1326 Eina_List *execs = NULL;
1330 /* if the Exec field appends multiple, that will run the list to the end,
1331 * causing this loop to only run once. otherwise, this loop will generate a
1332 * command for each file in the list. if the list is empty, this
1333 * will run once, removing any file field codes */
1339 int size = PATH_MAX;
1341 Efreet_Desktop_Command_File *file = eina_list_data_get(l);
1343 exec = malloc(size);
1344 p = command->desktop->exec;
1349 if (len >= size - 1)
1352 exec = realloc(exec, size);
1355 /* XXX handle fields inside quotes? */
1367 exec = efreet_desktop_command_append_single(exec, &size,
1378 exec = efreet_desktop_command_append_multiple(exec, &size,
1384 exec = efreet_desktop_command_append_icon(exec, &size, &len,
1388 exec = efreet_desktop_command_append_quoted(exec, &size, &len,
1389 command->desktop->name);
1392 exec = efreet_desktop_command_append_quoted(exec, &size, &len,
1393 command->desktop->orig_path);
1397 printf("[Efreet]: Deprecated conversion char: '%c' in file '%s'\n",
1398 *p, command->desktop->orig_path);
1405 printf("[Efreet]: Unknown conversion character: '%c'\n", *p);
1410 else exec[len++] = *p;
1415 /* NON-SPEC!!! this is to work around LOTS of 'broken' .desktop files that
1416 * do not specify %U/%u, %F/F etc. etc. at all. just a command. this is
1417 * unlikely to be fixed in distributions etc. in the long run as gnome/kde
1418 * seem to have workarounds too so no one notices.
1420 if ((file) && (!file_added))
1422 printf("[Efreet]: %s\n"
1424 " has no file path/uri spec info for executing this app WITH a\n"
1425 " file/uri as a parameter. This is unlikely to be the intent.\n"
1426 " please check the .desktop file and fix it by adding a %%U or %%F\n"
1427 " or something appropriate.",
1428 command->desktop->orig_path, command->desktop->exec);
1429 if (len >= size - 1)
1432 exec = realloc(exec, size);
1435 exec = efreet_desktop_command_append_multiple(exec, &size,
1436 &len, command, 'F');
1442 execs = eina_list_append(execs, exec);
1444 /* If no file was added, then the Exec field doesn't contain any file
1445 * fields (fFuUdDnN). We only want to run the app once in this case. */
1446 if (!file_added) break;
1448 while ((l = eina_list_next(l)) != NULL);
1454 efreet_desktop_command_free(Efreet_Desktop_Command *command)
1456 Efreet_Desktop_Command_File *dcf;
1458 if (!command) return;
1460 while (command->files)
1462 dcf = eina_list_data_get(command->files);
1463 efreet_desktop_command_file_free(dcf);
1464 command->files = eina_list_remove_list(command->files,
1471 efreet_desktop_command_append_quoted(char *dest, int *size, int *len, char *src)
1473 if (!src) return dest;
1474 dest = efreet_string_append(dest, size, len, "'");
1476 /* single quotes in src need to be escaped */
1477 if (strchr(src, '\''))
1484 dest = efreet_string_append(dest, size, len, "\'\\\'");
1486 dest = efreet_string_append_char(dest, size, len, *p);
1491 dest = efreet_string_append(dest, size, len, src);
1493 dest = efreet_string_append(dest, size, len, "'");
1499 efreet_desktop_command_append_multiple(char *dest, int *size, int *len,
1500 Efreet_Desktop_Command *command,
1503 Efreet_Desktop_Command_File *file;
1507 if (!command->files) return dest;
1509 EINA_LIST_FOREACH(command->files, l, file)
1514 dest = efreet_string_append_char(dest, size, len, ' ');
1516 dest = efreet_desktop_command_append_single(dest, size, len,
1517 file, tolower(type));
1524 efreet_desktop_command_append_single(char *dest, int *size, int *len,
1525 Efreet_Desktop_Command_File *file,
1532 str = file->fullpath;
1544 printf("Invalid type passed to efreet_desktop_command_append_single:"
1549 if (!str) return dest;
1551 dest = efreet_desktop_command_append_quoted(dest, size, len, str);
1557 efreet_desktop_command_append_icon(char *dest, int *size, int *len,
1558 Efreet_Desktop *desktop)
1560 if (!desktop->icon || !desktop->icon[0]) return dest;
1562 dest = efreet_string_append(dest, size, len, "--icon ");
1563 dest = efreet_desktop_command_append_quoted(dest, size, len, desktop->icon);
1570 * Append a string to a buffer, reallocating as necessary.
1573 efreet_string_append(char *dest, int *size, int *len, const char *src)
1578 l = ecore_strlcpy(dest + *len, src, *size - *len);
1580 while (l > *size - *len)
1582 /* we successfully appended this much */
1583 off += *size - *len - 1;
1586 dest = realloc(dest, *size);
1587 *(dest + *len) = '\0';
1589 l = ecore_strlcpy(dest + *len, src + off, *size - *len);
1597 efreet_string_append_char(char *dest, int *size, int *len, char c)
1599 if (*len >= *size - 1)
1602 dest = realloc(dest, *size);
1612 * @param command: the Efreet_Desktop_Comand that this file is for
1613 * @param file: the filname as either an absolute path, relative path, or URI
1615 static Efreet_Desktop_Command_File *
1616 efreet_desktop_command_file_process(Efreet_Desktop_Command *command, const char *file)
1618 Efreet_Desktop_Command_File *f;
1619 const char *uri, *base;
1622 printf("FLAGS: %d, %d, %d, %d\n",
1623 command->flags & EFREET_DESKTOP_EXEC_FLAG_FULLPATH ? 1 : 0,
1624 command->flags & EFREET_DESKTOP_EXEC_FLAG_URI ? 1 : 0,
1625 command->flags & EFREET_DESKTOP_EXEC_FLAG_DIR ? 1 : 0,
1626 command->flags & EFREET_DESKTOP_EXEC_FLAG_FILE ? 1 : 0);
1628 f = NEW(Efreet_Desktop_Command_File, 1);
1629 if (!f) return NULL;
1631 f->command = command;
1634 if(!strncmp(file, "http://", 7) || !strncmp(file, "ftp://", 6))
1637 base = ecore_file_file_get(file);
1641 else if (!strncmp(file, "file:", 5))
1643 file = efreet_desktop_command_file_uri_process(file);
1646 efreet_desktop_command_file_free(f);
1653 /* process non-local uri */
1654 if (command->flags & EFREET_DESKTOP_EXEC_FLAG_FULLPATH)
1658 snprintf(buf, PATH_MAX, "/tmp/%d-%d-%s", getpid(),
1659 efreet_desktop_command_file_id++, base);
1660 f->fullpath = strdup(buf);
1663 ecore_file_download(uri, f->fullpath, efreet_desktop_cb_download_complete,
1664 efreet_desktop_cb_download_progress, f);
1667 if (command->flags & EFREET_DESKTOP_EXEC_FLAG_URI)
1668 f->uri = strdup(uri);
1669 if (command->flags & EFREET_DESKTOP_EXEC_FLAG_DIR)
1670 f->dir = strdup("/tmp");
1671 if (command->flags & EFREET_DESKTOP_EXEC_FLAG_FILE)
1672 f->file = strdup(base);
1676 char *abs = efreet_desktop_command_path_absolute(file);
1677 /* process local uri/path */
1678 if (command->flags & EFREET_DESKTOP_EXEC_FLAG_FULLPATH)
1679 f->fullpath = strdup(abs);
1681 if (command->flags & EFREET_DESKTOP_EXEC_FLAG_URI)
1684 snprintf(buf, sizeof(buf), "file://%s", abs);
1685 f->uri = strdup(buf);
1687 if (command->flags & EFREET_DESKTOP_EXEC_FLAG_DIR)
1688 f->dir = ecore_file_dir_get(abs);
1689 if (command->flags & EFREET_DESKTOP_EXEC_FLAG_FILE)
1690 f->file = strdup(ecore_file_file_get(file));
1695 printf(" fullpath: %s\n", f->fullpath);
1696 printf(" uri: %s\n", f->uri);
1697 printf(" dir: %s\n", f->dir);
1698 printf(" file: %s\n", f->file);
1704 * @brief Find the local path portion of a file uri.
1705 * @param uri: a uri beginning with "file:"
1706 * @return the location of the path portion of the uri,
1707 * or NULL if the file is not on this machine
1710 efreet_desktop_command_file_uri_process(const char *uri)
1712 const char *path = NULL;
1713 int len = strlen(uri);
1715 /* uri:foo/bar => relative path foo/bar*/
1716 if (len >= 4 && uri[5] != '/')
1717 path = uri + strlen("file:");
1719 /* uri:/foo/bar => absolute path /foo/bar */
1720 else if (len >= 5 && uri[6] != '/')
1721 path = uri + strlen("file:");
1723 /* uri://foo/bar => absolute path /bar on machine foo */
1724 else if (len >= 6 && uri[7] != '/')
1727 char hostname[PATH_MAX];
1728 tmp = strdup(uri + 7);
1729 p = strchr(tmp, '/');
1733 if (!strcmp(tmp, "localhost"))
1734 path = uri + strlen("file://localhost");
1739 ret = gethostname(hostname, PATH_MAX);
1740 if ((ret == 0) && !strcmp(tmp, hostname))
1741 path = uri + strlen("file://") + strlen(hostname);
1747 /* uri:///foo/bar => absolute path /foo/bar on local machine */
1749 path = uri + strlen("file://");
1755 efreet_desktop_command_file_free(Efreet_Desktop_Command_File *file)
1759 IF_FREE(file->fullpath);
1762 IF_FREE(file->file);
1769 efreet_desktop_cb_download_complete(void *data, const char *file __UNUSED__,
1770 int status __UNUSED__)
1772 Efreet_Desktop_Command_File *f;
1776 /* XXX check status... error handling, etc */
1778 f->command->num_pending--;
1780 if (f->command->num_pending <= 0)
1783 execs = efreet_desktop_command_build(f->command);
1784 /* TODO: Need to handle the return value from efreet_desktop_command_execs_process */
1785 efreet_desktop_command_execs_process(f->command, execs);
1786 eina_list_free(execs);
1787 efreet_desktop_command_free(f->command);
1792 efreet_desktop_cb_download_progress(void *data,
1793 const char *file __UNUSED__,
1794 long int dltotal, long int dlnow,
1795 long int ultotal __UNUSED__,
1796 long int ulnow __UNUSED__)
1798 Efreet_Desktop_Command_File *dcf;
1801 if (dcf->command->cb_progress)
1802 return dcf->command->cb_progress(dcf->command->data,
1803 dcf->command->desktop,
1804 dcf->uri, dltotal, dlnow);
1810 * @brief Build an absolute path from an absolute or relative one.
1811 * @param path: an absolute or relative path
1812 * @return an allocated absolute path (must be freed)
1815 efreet_desktop_command_path_absolute(const char *path)
1818 int size = PATH_MAX;
1824 if (!(buf = malloc(size))) return NULL;
1825 if (!getcwd(buf, size))
1832 if (buf[len-1] != '/') buf = efreet_string_append(buf, &size, &len, "/");
1833 buf = efreet_string_append(buf, &size, &len, path);
1838 /* just dup an already absolute buffer */
1839 return strdup(path);