1 /* vim: set sw=4 ts=4 sts=4 et: */
10 #include <sys/types.h>
14 # include <winsock2.h>
17 #include <Ecore_Str.h>
18 #include <Ecore_File.h>
21 #include "efreet_private.h"
23 #define DESKTOP_VERSION 1.0
26 * The current desktop environment (e.g. "Enlightenment" or "Gnome")
28 static const char *desktop_environment = NULL;
31 * A cache of all loaded desktops, hashed by file name.
32 * Values are Efreet_Desktop structures
34 static Eina_Hash *efreet_desktop_cache = NULL;
37 * A list of the desktop types available
39 static Eina_List *efreet_desktop_types = NULL;
42 * A unique id for each tmp file created while building a command
44 static int efreet_desktop_command_file_id = 0;
47 static int cache_flush = 0;
49 EAPI int EFREET_DESKTOP_TYPE_APPLICATION = 0;
50 EAPI int EFREET_DESKTOP_TYPE_LINK = 0;
51 EAPI int EFREET_DESKTOP_TYPE_DIRECTORY = 0;
55 * Information about custom types
57 typedef struct Efreet_Desktop_Type_Info Efreet_Desktop_Type_Info;
58 struct Efreet_Desktop_Type_Info
62 Efreet_Desktop_Type_Parse_Cb parse_func;
63 Efreet_Desktop_Type_Save_Cb save_func;
64 Efreet_Desktop_Type_Free_Cb free_func;
67 static int efreet_desktop_read(Efreet_Desktop *desktop);
68 static void efreet_desktop_clear(Efreet_Desktop *desktop);
69 static Efreet_Desktop_Type_Info *efreet_desktop_type_parse(const char *type_str);
70 static void *efreet_desktop_application_fields_parse(Efreet_Desktop *desktop,
72 static void efreet_desktop_application_fields_save(Efreet_Desktop *desktop,
74 static void *efreet_desktop_link_fields_parse(Efreet_Desktop *desktop,
76 static void efreet_desktop_link_fields_save(Efreet_Desktop *desktop,
78 static int efreet_desktop_generic_fields_parse(Efreet_Desktop *desktop,
80 static void efreet_desktop_generic_fields_save(Efreet_Desktop *desktop,
82 static Eina_Bool efreet_desktop_x_fields_parse(const Eina_Hash *hash,
86 static Eina_Bool efreet_desktop_x_fields_save(const Eina_Hash *hash,
90 static int efreet_desktop_environment_check(Efreet_Ini *ini);
91 static char *efreet_string_append(char *dest, int *size,
92 int *len, const char *src);
93 static char *efreet_string_append_char(char *dest, int *size,
95 static Eina_List *efreet_desktop_command_build(Efreet_Desktop_Command *command);
96 static void efreet_desktop_command_free(Efreet_Desktop_Command *command);
97 static char *efreet_desktop_command_append_quoted(char *dest, int *size,
99 static char *efreet_desktop_command_append_icon(char *dest, int *size, int *len,
100 Efreet_Desktop *desktop);
101 static char *efreet_desktop_command_append_single(char *dest, int *size, int *len,
102 Efreet_Desktop_Command_File *file,
104 static char *efreet_desktop_command_append_multiple(char *dest, int *size, int *len,
105 Efreet_Desktop_Command *command,
108 static char *efreet_desktop_command_path_absolute(const char *path);
109 static Efreet_Desktop_Command_File *efreet_desktop_command_file_process(
110 Efreet_Desktop_Command *command,
112 static const char *efreet_desktop_command_file_uri_process(const char *uri);
113 static void efreet_desktop_command_file_free(Efreet_Desktop_Command_File *file);
115 static void efreet_desktop_cb_download_complete(void *data, const char *file,
117 static int efreet_desktop_cb_download_progress(void *data, const char *file,
118 long int dltotal, long int dlnow,
119 long int ultotal, long int ulnow);
122 static void *efreet_desktop_exec_cb(void *data, Efreet_Desktop *desktop,
123 char *exec, int remaining);
125 static void efreet_desktop_type_info_free(Efreet_Desktop_Type_Info *info);
126 static int efreet_desktop_command_flags_get(Efreet_Desktop *desktop);
127 static void *efreet_desktop_command_execs_process(Efreet_Desktop_Command *command, Eina_List *execs);
131 * @return Returns > 0 on success or 0 on failure
132 * @brief Initialize the Desktop parser subsystem
135 efreet_desktop_init(void)
137 if (init++) return init;
138 if (!eina_stringshare_init()) return --init;
139 if (!ecore_file_init()) return --init;
141 efreet_desktop_cache = eina_hash_string_superfast_new(NULL);
142 efreet_desktop_types = NULL;
144 EFREET_DESKTOP_TYPE_APPLICATION = efreet_desktop_type_add("Application",
145 efreet_desktop_application_fields_parse,
146 efreet_desktop_application_fields_save,
148 EFREET_DESKTOP_TYPE_LINK = efreet_desktop_type_add("Link",
149 efreet_desktop_link_fields_parse,
150 efreet_desktop_link_fields_save, NULL);
151 EFREET_DESKTOP_TYPE_DIRECTORY = efreet_desktop_type_add("Directory", NULL,
159 * @returns the number of initializations left for this system
160 * @brief Attempts to shut down the subsystem if nothing else is using it
163 efreet_desktop_shutdown(void)
165 Efreet_Desktop_Type_Info *info;
167 if (--init) return init;
168 ecore_file_shutdown();
169 eina_stringshare_shutdown();
171 IF_RELEASE(desktop_environment);
172 IF_FREE_HASH(efreet_desktop_cache);
173 while (efreet_desktop_types)
175 info = eina_list_data_get(efreet_desktop_types);
176 efreet_desktop_type_info_free(info);
177 efreet_desktop_types = eina_list_remove_list(efreet_desktop_types,
178 efreet_desktop_types);
186 * @param desktop: The desktop to check
187 * @return Returns 1 if the cache is still valid, 0 otherwise
188 * @brief This will check if the desktop cache is still valid.
191 efreet_desktop_cache_check(Efreet_Desktop *desktop)
193 if (!desktop) return 0;
195 /* have we modified this file since we last read it in? */
196 if ((desktop->cache_flush != cache_flush) ||
197 (ecore_file_mod_time(desktop->orig_path) != desktop->load_time))
204 * @param file: The file to get the Efreet_Desktop from
205 * @return Returns a reference to a cached Efreet_Desktop on success, NULL
206 * on failure. This reference should not be freed.
207 * @brief Gets a reference to an Efreet_Desktop structure representing the
208 * contents of @a file or NULL if @a file is not a valid .desktop file.
210 EAPI Efreet_Desktop *
211 efreet_desktop_get(const char *file)
213 Efreet_Desktop *desktop;
215 if (efreet_desktop_cache)
217 desktop = eina_hash_find(efreet_desktop_cache, file);
220 if (efreet_desktop_cache_check(desktop))
226 efreet_desktop_clear(desktop);
227 if (efreet_desktop_read(desktop))
230 desktop->cache_flush = cache_flush;
235 eina_hash_del(efreet_desktop_cache, file, NULL);
239 desktop = efreet_desktop_new(file);
240 if (!desktop) return NULL;
242 eina_hash_add(efreet_desktop_cache, file, desktop);
248 * @param desktop: The Efreet_Desktop to ref
249 * @return Returns the new reference count
252 efreet_desktop_ref(Efreet_Desktop *desktop)
254 if (!desktop) return 0;
260 * @param file: The file to create the Efreet_Desktop from
261 * @return Returns a new empty_Efreet_Desktop on success, NULL on failure
262 * @brief Creates a new empty Efreet_Desktop structure or NULL on failure
264 EAPI Efreet_Desktop *
265 efreet_desktop_empty_new(const char *file)
267 Efreet_Desktop *desktop;
269 desktop = NEW(Efreet_Desktop, 1);
270 if (!desktop) return NULL;
272 desktop->orig_path = strdup(file);
273 desktop->load_time = ecore_file_mod_time(file);
282 * @param file: The file to create the Efreet_Desktop from
283 * @return Returns a new Efreet_Desktop on success, NULL on failure
284 * @brief Creates a new Efreet_Desktop structure initialized from the
285 * contents of @a file or NULL on failure
287 EAPI Efreet_Desktop *
288 efreet_desktop_new(const char *file)
290 Efreet_Desktop *desktop;
292 if (!ecore_file_exists(file)) return NULL;
294 desktop = NEW(Efreet_Desktop, 1);
295 if (!desktop) return NULL;
297 desktop->orig_path = strdup(file);
299 if (!efreet_desktop_read(desktop))
301 efreet_desktop_free(desktop);
306 desktop->cache_flush = cache_flush;
313 * @param desktop: The desktop to fill
314 * @return Returns 1 on success, 0 on failure
315 * @brief initialize an Efreet_Desktop from the contents of @a file
318 efreet_desktop_read(Efreet_Desktop *desktop)
324 ini = efreet_ini_new(desktop->orig_path);
327 efreet_ini_free(ini);
331 ok = efreet_ini_section_set(ini, "Desktop Entry");
332 if (!ok) ok = efreet_ini_section_set(ini, "KDE Desktop Entry");
335 printf("efreet_desktop_new error: no Desktop Entry section\n");
341 Efreet_Desktop_Type_Info *info;
343 info = efreet_desktop_type_parse(efreet_ini_string_get(ini, "Type"));
346 desktop->type = info->id;
347 desktop->version = efreet_ini_double_get(ini, "Version");
349 if (info->parse_func)
350 desktop->type_data = info->parse_func(desktop, ini);
356 if (!error && !efreet_desktop_environment_check(ini)) error = 1;
357 if (!error && !efreet_desktop_generic_fields_parse(desktop, ini)) error = 1;
359 eina_hash_foreach(ini->section, efreet_desktop_x_fields_parse, desktop);
361 efreet_ini_free(ini);
363 desktop->load_time = ecore_file_mod_time(desktop->orig_path);
372 * @param desktop: The Efreet_Desktop to work with
373 * @return Returns no value
374 * @brief Frees the Efreet_Desktop's data
377 efreet_desktop_clear(Efreet_Desktop *desktop)
381 IF_FREE(desktop->name);
382 IF_FREE(desktop->generic_name);
383 IF_FREE(desktop->comment);
384 IF_FREE(desktop->icon);
385 IF_FREE(desktop->url);
387 IF_FREE(desktop->try_exec);
388 IF_FREE(desktop->exec);
389 IF_FREE(desktop->path);
390 IF_FREE(desktop->startup_wm_class);
392 IF_FREE_LIST(desktop->only_show_in, free);
393 IF_FREE_LIST(desktop->not_show_in, free);
394 while (desktop->categories)
396 data = eina_list_data_get(desktop->categories);
397 eina_stringshare_del(data);
398 desktop->categories = eina_list_remove_list(desktop->categories, desktop->categories);
400 while (desktop->mime_types)
402 data = eina_list_data_get(desktop->mime_types);
403 eina_stringshare_del(data);
404 desktop->mime_types = eina_list_remove_list(desktop->mime_types, desktop->mime_types);
407 IF_FREE_HASH(desktop->x);
409 if (desktop->type_data)
411 Efreet_Desktop_Type_Info *info;
412 info = eina_list_nth(efreet_desktop_types, desktop->type);
414 info->free_func(desktop->type_data);
419 * @param desktop: The desktop file to save
420 * @return Returns 1 on success or 0 on failure
421 * @brief Saves any changes made to @a desktop back to the file on the
425 efreet_desktop_save(Efreet_Desktop *desktop)
427 Efreet_Desktop_Type_Info *info;
431 ini = efreet_ini_new(desktop->orig_path);
432 efreet_ini_section_add(ini, "Desktop Entry");
433 efreet_ini_section_set(ini, "Desktop Entry");
435 info = eina_list_nth(efreet_desktop_types, desktop->type);
438 efreet_ini_string_set(ini, "Type", info->type);
439 if (info->save_func) info->save_func(desktop, ini);
448 if (desktop->only_show_in)
450 val = efreet_desktop_string_list_join(desktop->only_show_in);
451 efreet_ini_string_set(ini, "OnlyShowIn", val);
454 if (desktop->not_show_in)
456 val = efreet_desktop_string_list_join(desktop->not_show_in);
457 efreet_ini_string_set(ini, "NotShowIn", val);
460 efreet_desktop_generic_fields_save(desktop, ini);
461 /* When we save the file, it should be updated to the
462 * latest version that we support! */
463 efreet_ini_double_set(ini, "Version", DESKTOP_VERSION);
465 if (!efreet_ini_save(ini, desktop->orig_path)) ok = 0;
468 if (desktop != eina_hash_find(efreet_desktop_cache, desktop->orig_path))
471 eina_hash_del(efreet_desktop_cache, desktop->orig_path, NULL);
472 eina_hash_add(efreet_desktop_cache, desktop->orig_path,
477 efreet_ini_free(ini);
482 * @param desktop: The desktop file to save
483 * @param file: The filename to save as
484 * @return Returns 1 on success or 0 on failure
485 * @brief Saves @a desktop to @a file
488 efreet_desktop_save_as(Efreet_Desktop *desktop, const char *file)
490 if (desktop == eina_hash_find(efreet_desktop_cache, desktop->orig_path))
493 eina_hash_del(efreet_desktop_cache, desktop->orig_path, NULL);
495 FREE(desktop->orig_path);
496 desktop->orig_path = strdup(file);
497 return efreet_desktop_save(desktop);
502 * @param desktop: The Efreet_Desktop to work with
503 * @return Returns no value
504 * @brief Frees the Efreet_Desktop structure and all of it's data
507 efreet_desktop_free(Efreet_Desktop *desktop)
511 if (!desktop) return;
514 if (desktop->ref > 0) return;
516 if (desktop->cached && efreet_desktop_cache)
517 eina_hash_del(efreet_desktop_cache, desktop->orig_path, NULL);
519 IF_FREE(desktop->orig_path);
521 IF_FREE(desktop->name);
522 IF_FREE(desktop->generic_name);
523 IF_FREE(desktop->comment);
524 IF_FREE(desktop->icon);
525 IF_FREE(desktop->url);
527 IF_FREE(desktop->try_exec);
528 IF_FREE(desktop->exec);
529 IF_FREE(desktop->path);
530 IF_FREE(desktop->startup_wm_class);
532 IF_FREE_LIST(desktop->only_show_in, free);
533 IF_FREE_LIST(desktop->not_show_in, free);
535 EINA_LIST_FREE(desktop->categories, str)
536 eina_stringshare_del(str);
537 EINA_LIST_FREE(desktop->mime_types, str)
538 eina_stringshare_del(str);
540 IF_FREE_HASH(desktop->x);
542 if (desktop->type_data)
544 Efreet_Desktop_Type_Info *info;
545 info = eina_list_nth(efreet_desktop_types, desktop->type);
547 info->free_func(desktop->type_data);
554 * @param desktop: The desktop file to work with
555 * @param files: The files to be substituted into the exec line
556 * @param data: The data pointer to pass
557 * @return Returns the Ecore_Exce for @a desktop
558 * @brief Parses the @a desktop exec line and returns an Ecore_Exe.
561 efreet_desktop_exec(Efreet_Desktop *desktop, Eina_List *files, void *data)
563 efreet_desktop_command_get(desktop, files, efreet_desktop_exec_cb, data);
567 efreet_desktop_exec_cb(void *data, Efreet_Desktop *desktop __UNUSED__,
568 char *exec, int remaining __UNUSED__)
571 ecore_exe_run(exec, data);
578 * @param environment: the environment name
579 * @brief sets the global desktop environment name
582 efreet_desktop_environment_set(const char *environment)
584 if (desktop_environment) eina_stringshare_del(desktop_environment);
585 if (environment) desktop_environment = eina_stringshare_add(environment);
586 else desktop_environment = NULL;
590 * @return environment: the environment name
591 * @brief sets the global desktop environment name
594 efreet_desktop_environment_get(void)
596 return desktop_environment;
600 * @param desktop: The desktop to work with
601 * @return Returns the number of categories assigned to this desktop
602 * @brief Retrieves the number of categories the given @a desktop belongs
606 efreet_desktop_category_count_get(Efreet_Desktop *desktop)
608 if (!desktop || !desktop->categories) return 0;
609 return eina_list_count(desktop->categories);
613 * @param desktop: the desktop
614 * @param category: the category name
615 * @brief add a category to a desktop
618 efreet_desktop_category_add(Efreet_Desktop *desktop, const char *category)
620 if (!desktop) return;
622 if (eina_list_search_unsorted(desktop->categories,
623 EINA_COMPARE_CB(strcmp), category)) return;
625 desktop->categories = eina_list_append(desktop->categories,
626 (void *)eina_stringshare_add(category));
630 * @param desktop: the desktop
631 * @param category: the category name
632 * @brief removes a category from a desktop
633 * @return 1 if the desktop had his category listed, 0 otherwise
636 efreet_desktop_category_del(Efreet_Desktop *desktop, const char *category)
640 if (!desktop || !desktop->categories) return 0;
642 if ((found = eina_list_search_unsorted(desktop->categories,
643 EINA_COMPARE_CB(strcmp), category)))
645 eina_stringshare_del(found);
646 desktop->categories = eina_list_remove(desktop->categories, found);
655 * @param type: The type to add to the list of matching types
656 * @param parse_func: a function to parse out custom fields
657 * @param save_func: a function to save data returned from @a parse_func
658 * @param free_func: a function to free data returned from @a parse_func
659 * @return Returns the id of the new type
660 * @brief Adds the given type to the list of types in the system
663 efreet_desktop_type_add(const char *type, Efreet_Desktop_Type_Parse_Cb parse_func,
664 Efreet_Desktop_Type_Save_Cb save_func,
665 Efreet_Desktop_Type_Free_Cb free_func)
668 Efreet_Desktop_Type_Info *info;
670 info = NEW(Efreet_Desktop_Type_Info, 1);
673 id = eina_list_count(efreet_desktop_types);
676 info->type = strdup(type);
677 info->parse_func = parse_func;
678 info->save_func = save_func;
679 info->free_func = free_func;
681 efreet_desktop_types = eina_list_append(efreet_desktop_types, info);
687 * @brief Add an alias for an existing desktop type.
688 * @param from_type the type to alias (e.g. EFREE_DESKTOP_TYPE_APPLICATION)
689 * @param alias the alias
690 * @return the new type id, or -1 if @p from_type was not valid
692 * This allows applications to add non-standard types that behave exactly as standard types.
695 efreet_desktop_type_alias(int from_type, const char *alias)
697 Efreet_Desktop_Type_Info *info;
698 info = eina_list_nth(efreet_desktop_types, from_type);
699 if (!info) return -1;
701 return efreet_desktop_type_add(alias, info->parse_func, info->save_func, info->free_func);
706 * @brief Free an Efreet Desktop_Type_Info struct
709 efreet_desktop_type_info_free(Efreet_Desktop_Type_Info *info)
717 * @brief get type specific data for custom desktop types
718 * @param desktop the desktop
719 * @return type specific data, or NULL if there is none
722 efreet_desktop_type_data_get(Efreet_Desktop *desktop)
724 return desktop->type_data;
729 * @param type_str: the type as a string
730 * @return the parsed type
731 * @brief parse the type string into an Efreet_Desktop_Type
733 static Efreet_Desktop_Type_Info *
734 efreet_desktop_type_parse(const char *type_str)
736 Efreet_Desktop_Type_Info *info;
739 if (!type_str) return NULL;
741 EINA_LIST_FOREACH(efreet_desktop_types, l, info)
743 if (!strcmp(info->type, type_str))
751 * @param string: the raw string list
752 * @return an Eina_List of ecore string's
753 * @brief Parse ';' separate list of strings according to the desktop spec
756 efreet_desktop_string_list_parse(const char *string)
758 Eina_List *list = NULL;
762 if (!string) return NULL;
764 tmp = strdup(string);
767 while ((p = strchr(s, ';')))
769 if (p > tmp && *(p-1) == '\\') continue;
771 list = eina_list_append(list, (void *)eina_stringshare_add(s));
774 /* If this is true, the .desktop file does not follow the standard */
778 printf("[Efreet]: Found a string list without ';' "
779 "at the end: %s\n", string);
781 list = eina_list_append(list, (void *)eina_stringshare_add(s));
790 * @param list: Eina_List with strings
791 * @return a raw string list
792 * @brief Create a ';' separate list of strings according to the desktop spec
795 efreet_desktop_string_list_join(Eina_List *list)
800 size_t size, pos, len;
802 if (!list) return strdup("");
805 string = malloc(size);
808 EINA_LIST_FOREACH(list, l, tmp)
812 if ((len + pos + 1) >= size)
814 size = len + pos + 1024;
815 string = realloc(string, size);
817 strcpy(string + pos, tmp);
819 strcpy(string + pos, ";");
826 * @brief Tell Efreet to flush any cached desktop entries so it reloads on get.
828 * This flags the cache to be invalid, so next time a desktop file is fetched
829 * it will force it to be re-read off disk next time efreet_desktop_get() is
833 efreet_desktop_cache_flush(void)
840 * @param desktop: the Efreet_Desktop to store parsed fields in
841 * @param ini: the Efreet_Ini to parse fields from
843 * @brief Parse application specific desktop fields
846 efreet_desktop_application_fields_parse(Efreet_Desktop *desktop, Efreet_Ini *ini)
850 val = efreet_ini_string_get(ini, "TryExec");
851 if (val) desktop->try_exec = strdup(val);
853 val = efreet_ini_string_get(ini, "Exec");
854 if (val) desktop->exec = strdup(val);
856 val = efreet_ini_string_get(ini, "Path");
857 if (val) desktop->path = strdup(val);
859 val = efreet_ini_string_get(ini, "StartupWMClass");
860 if (val) desktop->startup_wm_class = strdup(val);
862 desktop->categories = efreet_desktop_string_list_parse(
863 efreet_ini_string_get(ini, "Categories"));
864 desktop->mime_types = efreet_desktop_string_list_parse(
865 efreet_ini_string_get(ini, "MimeType"));
867 desktop->terminal = efreet_ini_boolean_get(ini, "Terminal");
868 desktop->startup_notify = efreet_ini_boolean_get(ini, "StartupNotify");
875 * @param desktop: the Efreet_Desktop to save fields from
876 * @param ini: the Efreet_Ini to save fields to
877 * @return Returns no value
878 * @brief Save application specific desktop fields
881 efreet_desktop_application_fields_save(Efreet_Desktop *desktop, Efreet_Ini *ini)
885 if (desktop->try_exec)
886 efreet_ini_string_set(ini, "TryExec", desktop->try_exec);
889 efreet_ini_string_set(ini, "Exec", desktop->exec);
892 efreet_ini_string_set(ini, "Path", desktop->path);
894 if (desktop->startup_wm_class)
895 efreet_ini_string_set(ini, "StartupWMClass", desktop->startup_wm_class);
897 if (desktop->categories)
899 val = efreet_desktop_string_list_join(desktop->categories);
900 efreet_ini_string_set(ini, "Categories", val);
904 if (desktop->mime_types)
906 val = efreet_desktop_string_list_join(desktop->mime_types);
907 efreet_ini_string_set(ini, "MimeType", val);
911 efreet_ini_boolean_set(ini, "Terminal", desktop->terminal);
912 efreet_ini_boolean_set(ini, "StartupNotify", desktop->startup_notify);
917 * @param desktop: the Efreet_Desktop to store parsed fields in
918 * @param ini: the Efreet_Ini to parse fields from
919 * @return Returns no value
920 * @brief Parse link specific desktop fields
923 efreet_desktop_link_fields_parse(Efreet_Desktop *desktop, Efreet_Ini *ini)
927 val = efreet_ini_string_get(ini, "URL");
928 if (val) desktop->url = strdup(val);
934 * @param desktop: the Efreet_Desktop to save fields from
935 * @param ini: the Efreet_Ini to save fields in
936 * @return Returns no value
937 * @brief Save link specific desktop fields
940 efreet_desktop_link_fields_save(Efreet_Desktop *desktop, Efreet_Ini *ini)
942 if (desktop->url) efreet_ini_string_set(ini, "URL", desktop->url);
947 * @param desktop: the Efreet_Desktop to store parsed fields in
948 * @param ini: the Efreet_Ini to parse fields from
949 * @return 1 if parsed succesfully, 0 otherwise
950 * @brief Parse desktop fields that all types can include
953 efreet_desktop_generic_fields_parse(Efreet_Desktop *desktop, Efreet_Ini *ini)
957 val = efreet_ini_localestring_get(ini, "Name");
958 if (val) desktop->name = strdup(val);
961 printf("efreet_desktop_generic_fields_parse error: no Name\n");
965 val = efreet_ini_localestring_get(ini, "GenericName");
966 if (val) desktop->generic_name = strdup(val);
968 val = efreet_ini_localestring_get(ini, "Comment");
969 if (val) desktop->comment = strdup(val);
971 val = efreet_ini_localestring_get(ini, "Icon");
972 if (val) desktop->icon = strdup(val);
974 desktop->no_display = efreet_ini_boolean_get(ini, "NoDisplay");
975 desktop->hidden = efreet_ini_boolean_get(ini, "Hidden");
982 * @param desktop: the Efreet_Desktop to save fields from
983 * @param ini: the Efreet_Ini to save fields to
984 * @return Returns nothing
985 * @brief Save desktop fields that all types can include
988 efreet_desktop_generic_fields_save(Efreet_Desktop *desktop, Efreet_Ini *ini)
994 efreet_ini_localestring_set(ini, "Name", desktop->name);
995 val = efreet_ini_string_get(ini, "Name");
997 efreet_ini_string_set(ini, "Name", desktop->name);
999 if (desktop->generic_name)
1001 efreet_ini_localestring_set(ini, "GenericName", desktop->generic_name);
1002 val = efreet_ini_string_get(ini, "GenericName");
1004 efreet_ini_string_set(ini, "GenericName", desktop->generic_name);
1006 if (desktop->comment)
1008 efreet_ini_localestring_set(ini, "Comment", desktop->comment);
1009 val = efreet_ini_string_get(ini, "Comment");
1011 efreet_ini_string_set(ini, "Comment", desktop->comment);
1015 efreet_ini_localestring_set(ini, "Icon", desktop->icon);
1016 val = efreet_ini_string_get(ini, "Icon");
1018 efreet_ini_string_set(ini, "Icon", desktop->icon);
1021 efreet_ini_boolean_set(ini, "NoDisplay", desktop->no_display);
1022 efreet_ini_boolean_set(ini, "Hidden", desktop->hidden);
1024 if (desktop->x) eina_hash_foreach(desktop->x, efreet_desktop_x_fields_save,
1030 * @param node: The node to work with
1031 * @param desktop: The desktop file to work with
1032 * @return Returns always true, to be used in eina_hash_foreach()
1033 * @brief Parses out an X- key from @a node and stores in @a desktop
1036 efreet_desktop_x_fields_parse(const Eina_Hash *hash __UNUSED__, const void *key, void *value, void *fdata)
1038 Efreet_Desktop * desktop = fdata;
1040 if (!desktop) return EINA_TRUE;
1041 if (strncmp(key, "X-", 2)) return EINA_TRUE;
1044 desktop->x = eina_hash_string_superfast_new(EINA_FREE_CB(eina_stringshare_del));
1045 eina_hash_del(desktop->x, key, NULL);
1046 eina_hash_add(desktop->x, key, (void *)eina_stringshare_add(value));
1053 * @param node: The node to work with
1054 * @param ini: The ini file to work with
1055 * @return Returns no value
1056 * @brief Stores an X- key from @a node and stores in @a ini
1059 efreet_desktop_x_fields_save(const Eina_Hash *hash __UNUSED__, const void *key, void *value, void *fdata)
1061 Efreet_Ini *ini = fdata;
1062 efreet_ini_string_set(ini, key, value);
1070 * @param ini: The Efreet_Ini to parse values from
1071 * @return 1 if desktop should be included in current environement, 0 otherwise
1072 * @brief Determines if a desktop should be included in the current environment,
1073 * based on the values of the OnlyShowIn and NotShowIn fields
1076 efreet_desktop_environment_check(Efreet_Ini *ini)
1082 if (!desktop_environment)
1085 list = efreet_desktop_string_list_parse(efreet_ini_string_get(ini, "OnlyShowIn"));
1088 EINA_LIST_FREE(list, val)
1090 if (!strcmp(val, desktop_environment))
1092 eina_stringshare_del(val);
1098 list = efreet_desktop_string_list_parse(efreet_ini_string_get(ini, "NotShowIn"));
1099 EINA_LIST_FREE(list, val)
1101 if (!strcmp(val, desktop_environment))
1103 eina_stringshare_del(val);
1111 * @param desktop: the desktop entry
1112 * @param files: an eina list of file names to execute, as either absolute paths,
1113 * relative paths, or uris
1114 * @param func: a callback to call for each prepared command line
1115 * @param data: user data passed to the callback
1116 * @return Returns the return value of @p func on success or NULL on failure
1117 * @brief Get a command to use to execute a desktop entry.
1120 efreet_desktop_command_get(Efreet_Desktop *desktop, Eina_List *files,
1121 Efreet_Desktop_Command_Cb func, void *data)
1123 return efreet_desktop_command_progress_get(desktop, files, func, NULL, data);
1127 * @param desktop: the desktop entry
1128 * @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)
1129 * @return Returns an eina list of exec strings
1130 * @brief Get the command to use to execute a desktop entry
1132 * The returned list and each of its elements must be freed.
1135 efreet_desktop_command_local_get(Efreet_Desktop *desktop, Eina_List *files)
1137 Efreet_Desktop_Command *command;
1139 Eina_List *execs, *l;
1141 if (!desktop || !desktop->exec) return NULL;
1143 command = NEW(Efreet_Desktop_Command, 1);
1144 if (!command) return 0;
1146 command->desktop = desktop;
1148 command->flags = efreet_desktop_command_flags_get(desktop);
1149 /* get the required info for each file passed in */
1152 EINA_LIST_FOREACH(files, l, file)
1154 Efreet_Desktop_Command_File *dcf;
1156 dcf = efreet_desktop_command_file_process(command, file);
1160 efreet_desktop_command_file_free(dcf);
1163 command->files = eina_list_append(command->files, dcf);
1167 execs = efreet_desktop_command_build(command);
1168 efreet_desktop_command_free(command);
1175 * @param desktop: the desktop entry
1176 * @param files: an eina list of file names to execute, as either absolute paths,
1177 * relative paths, or uris
1178 * @param cb_command: a callback to call for each prepared command line
1179 * @param cb_progress: a callback to get progress for the downloads
1180 * @param data: user data passed to the callback
1181 * @return Returns 1 on success or 0 on failure
1182 * @brief Get a command to use to execute a desktop entry, and receive progress
1183 * updates for downloading of remote URI's passed in.
1186 efreet_desktop_command_progress_get(Efreet_Desktop *desktop, Eina_List *files,
1187 Efreet_Desktop_Command_Cb cb_command,
1188 Efreet_Desktop_Progress_Cb cb_progress,
1191 Efreet_Desktop_Command *command;
1196 if (!desktop || !cb_command || !desktop->exec) return NULL;
1198 command = NEW(Efreet_Desktop_Command, 1);
1199 if (!command) return NULL;
1201 command->cb_command = cb_command;
1202 command->cb_progress = cb_progress;
1203 command->data = data;
1204 command->desktop = desktop;
1206 command->flags = efreet_desktop_command_flags_get(desktop);
1207 /* get the required info for each file passed in */
1210 EINA_LIST_FOREACH(files, l, file)
1212 Efreet_Desktop_Command_File *dcf;
1214 dcf = efreet_desktop_command_file_process(command, file);
1216 command->files = eina_list_append(command->files, dcf);
1217 command->num_pending += dcf->pending;
1221 if (command->num_pending == 0)
1224 execs = efreet_desktop_command_build(command);
1225 ret = efreet_desktop_command_execs_process(command, execs);
1226 eina_list_free(execs);
1227 efreet_desktop_command_free(command);
1236 * @brief Determine which file related field codes are present in the Exec string of a .desktop
1237 * @params desktop and Efreet Desktop
1238 * @return a bitmask of file field codes present in exec string
1241 efreet_desktop_command_flags_get(Efreet_Desktop *desktop)
1245 /* first, determine which fields are present in the Exec string */
1246 p = strchr(desktop->exec, '%');
1254 flags |= EFREET_DESKTOP_EXEC_FLAG_FULLPATH;
1258 flags |= EFREET_DESKTOP_EXEC_FLAG_URI;
1262 flags |= EFREET_DESKTOP_EXEC_FLAG_DIR;
1266 flags |= EFREET_DESKTOP_EXEC_FLAG_FILE;
1278 /* NON-SPEC!!! this is to work around LOTS of 'broken' .desktop files that
1279 * do not specify %U/%u, %F/F etc. etc. at all. just a command. this is
1280 * unlikely to be fixed in distributions etc. in the long run as gnome/kde
1281 * seem to have workarounds too so no one notices.
1283 if (!flags) flags |= EFREET_DESKTOP_EXEC_FLAG_FULLPATH;
1293 * @brief Call the command callback for each exec in the list
1298 efreet_desktop_command_execs_process(Efreet_Desktop_Command *command, Eina_List *execs)
1305 num = eina_list_count(execs);
1306 EINA_LIST_FOREACH(execs, l, exec)
1308 ret = command->cb_command(command->data, command->desktop, exec, --num);
1315 * @brief Builds the actual exec string from the raw string and a list of
1316 * processed filename information. The callback passed in to
1317 * efreet_desktop_command_get is called for each exec string created.
1319 * @param command: the command to build
1320 * @return a list of executable strings
1323 efreet_desktop_command_build(Efreet_Desktop_Command *command)
1325 Eina_List *execs = NULL;
1329 /* if the Exec field appends multiple, that will run the list to the end,
1330 * causing this loop to only run once. otherwise, this loop will generate a
1331 * command for each file in the list. if the list is empty, this
1332 * will run once, removing any file field codes */
1338 int size = PATH_MAX;
1340 Efreet_Desktop_Command_File *file = eina_list_data_get(l);
1342 exec = malloc(size);
1343 p = command->desktop->exec;
1348 if (len >= size - 1)
1351 exec = realloc(exec, size);
1354 /* XXX handle fields inside quotes? */
1366 exec = efreet_desktop_command_append_single(exec, &size,
1377 exec = efreet_desktop_command_append_multiple(exec, &size,
1383 exec = efreet_desktop_command_append_icon(exec, &size, &len,
1387 exec = efreet_desktop_command_append_quoted(exec, &size, &len,
1388 command->desktop->name);
1391 exec = efreet_desktop_command_append_quoted(exec, &size, &len,
1392 command->desktop->orig_path);
1396 printf("[Efreet]: Deprecated conversion char: '%c' in file '%s'\n",
1397 *p, command->desktop->orig_path);
1404 printf("[Efreet]: Unknown conversion character: '%c'\n", *p);
1409 else exec[len++] = *p;
1414 /* NON-SPEC!!! this is to work around LOTS of 'broken' .desktop files that
1415 * do not specify %U/%u, %F/F etc. etc. at all. just a command. this is
1416 * unlikely to be fixed in distributions etc. in the long run as gnome/kde
1417 * seem to have workarounds too so no one notices.
1419 if ((file) && (!file_added))
1421 printf("[Efreet]: %s\n"
1423 " has no file path/uri spec info for executing this app WITH a\n"
1424 " file/uri as a parameter. This is unlikely to be the intent.\n"
1425 " please check the .desktop file and fix it by adding a %%U or %%F\n"
1426 " or something appropriate.",
1427 command->desktop->orig_path, command->desktop->exec);
1428 if (len >= size - 1)
1431 exec = realloc(exec, size);
1434 exec = efreet_desktop_command_append_multiple(exec, &size,
1435 &len, command, 'F');
1441 execs = eina_list_append(execs, exec);
1443 /* If no file was added, then the Exec field doesn't contain any file
1444 * fields (fFuUdDnN). We only want to run the app once in this case. */
1445 if (!file_added) break;
1447 while ((l = eina_list_next(l)) != NULL);
1453 efreet_desktop_command_free(Efreet_Desktop_Command *command)
1455 Efreet_Desktop_Command_File *dcf;
1457 if (!command) return;
1459 while (command->files)
1461 dcf = eina_list_data_get(command->files);
1462 efreet_desktop_command_file_free(dcf);
1463 command->files = eina_list_remove_list(command->files,
1470 efreet_desktop_command_append_quoted(char *dest, int *size, int *len, char *src)
1472 if (!src) return dest;
1473 dest = efreet_string_append(dest, size, len, "'");
1475 /* single quotes in src need to be escaped */
1476 if (strchr(src, '\''))
1483 dest = efreet_string_append(dest, size, len, "\'\\\'");
1485 dest = efreet_string_append_char(dest, size, len, *p);
1490 dest = efreet_string_append(dest, size, len, src);
1492 dest = efreet_string_append(dest, size, len, "'");
1498 efreet_desktop_command_append_multiple(char *dest, int *size, int *len,
1499 Efreet_Desktop_Command *command,
1502 Efreet_Desktop_Command_File *file;
1506 if (!command->files) return dest;
1508 EINA_LIST_FOREACH(command->files, l, file)
1513 dest = efreet_string_append_char(dest, size, len, ' ');
1515 dest = efreet_desktop_command_append_single(dest, size, len,
1516 file, tolower(type));
1523 efreet_desktop_command_append_single(char *dest, int *size, int *len,
1524 Efreet_Desktop_Command_File *file,
1531 str = file->fullpath;
1543 printf("Invalid type passed to efreet_desktop_command_append_single:"
1548 if (!str) return dest;
1550 dest = efreet_desktop_command_append_quoted(dest, size, len, str);
1556 efreet_desktop_command_append_icon(char *dest, int *size, int *len,
1557 Efreet_Desktop *desktop)
1559 if (!desktop->icon || !desktop->icon[0]) return dest;
1561 dest = efreet_string_append(dest, size, len, "--icon ");
1562 dest = efreet_desktop_command_append_quoted(dest, size, len, desktop->icon);
1569 * Append a string to a buffer, reallocating as necessary.
1572 efreet_string_append(char *dest, int *size, int *len, const char *src)
1577 l = ecore_strlcpy(dest + *len, src, *size - *len);
1579 while (l > *size - *len)
1581 /* we successfully appended this much */
1582 off += *size - *len - 1;
1585 dest = realloc(dest, *size);
1586 *(dest + *len) = '\0';
1588 l = ecore_strlcpy(dest + *len, src + off, *size - *len);
1596 efreet_string_append_char(char *dest, int *size, int *len, char c)
1598 if (*len >= *size - 1)
1601 dest = realloc(dest, *size);
1611 * @param command: the Efreet_Desktop_Comand that this file is for
1612 * @param file: the filname as either an absolute path, relative path, or URI
1614 static Efreet_Desktop_Command_File *
1615 efreet_desktop_command_file_process(Efreet_Desktop_Command *command, const char *file)
1617 Efreet_Desktop_Command_File *f;
1618 const char *uri, *base;
1621 printf("FLAGS: %d, %d, %d, %d\n",
1622 command->flags & EFREET_DESKTOP_EXEC_FLAG_FULLPATH ? 1 : 0,
1623 command->flags & EFREET_DESKTOP_EXEC_FLAG_URI ? 1 : 0,
1624 command->flags & EFREET_DESKTOP_EXEC_FLAG_DIR ? 1 : 0,
1625 command->flags & EFREET_DESKTOP_EXEC_FLAG_FILE ? 1 : 0);
1627 f = NEW(Efreet_Desktop_Command_File, 1);
1628 if (!f) return NULL;
1630 f->command = command;
1633 if(!strncmp(file, "http://", 7) || !strncmp(file, "ftp://", 6))
1636 base = ecore_file_file_get(file);
1640 else if (!strncmp(file, "file:", 5))
1642 file = efreet_desktop_command_file_uri_process(file);
1645 efreet_desktop_command_file_free(f);
1652 /* process non-local uri */
1653 if (command->flags & EFREET_DESKTOP_EXEC_FLAG_FULLPATH)
1657 snprintf(buf, PATH_MAX, "/tmp/%d-%d-%s", getpid(),
1658 efreet_desktop_command_file_id++, base);
1659 f->fullpath = strdup(buf);
1662 ecore_file_download(uri, f->fullpath, efreet_desktop_cb_download_complete,
1663 efreet_desktop_cb_download_progress, f);
1666 if (command->flags & EFREET_DESKTOP_EXEC_FLAG_URI)
1667 f->uri = strdup(uri);
1668 if (command->flags & EFREET_DESKTOP_EXEC_FLAG_DIR)
1669 f->dir = strdup("/tmp");
1670 if (command->flags & EFREET_DESKTOP_EXEC_FLAG_FILE)
1671 f->file = strdup(base);
1675 char *abs = efreet_desktop_command_path_absolute(file);
1676 /* process local uri/path */
1677 if (command->flags & EFREET_DESKTOP_EXEC_FLAG_FULLPATH)
1678 f->fullpath = strdup(abs);
1680 if (command->flags & EFREET_DESKTOP_EXEC_FLAG_URI)
1683 snprintf(buf, sizeof(buf), "file://%s", abs);
1684 f->uri = strdup(buf);
1686 if (command->flags & EFREET_DESKTOP_EXEC_FLAG_DIR)
1687 f->dir = ecore_file_dir_get(abs);
1688 if (command->flags & EFREET_DESKTOP_EXEC_FLAG_FILE)
1689 f->file = strdup(ecore_file_file_get(file));
1694 printf(" fullpath: %s\n", f->fullpath);
1695 printf(" uri: %s\n", f->uri);
1696 printf(" dir: %s\n", f->dir);
1697 printf(" file: %s\n", f->file);
1703 * @brief Find the local path portion of a file uri.
1704 * @param uri: a uri beginning with "file:"
1705 * @return the location of the path portion of the uri,
1706 * or NULL if the file is not on this machine
1709 efreet_desktop_command_file_uri_process(const char *uri)
1711 const char *path = NULL;
1712 int len = strlen(uri);
1714 /* uri:foo/bar => relative path foo/bar*/
1715 if (len >= 4 && uri[5] != '/')
1716 path = uri + strlen("file:");
1718 /* uri:/foo/bar => absolute path /foo/bar */
1719 else if (len >= 5 && uri[6] != '/')
1720 path = uri + strlen("file:");
1722 /* uri://foo/bar => absolute path /bar on machine foo */
1723 else if (len >= 6 && uri[7] != '/')
1726 char hostname[PATH_MAX];
1727 tmp = strdup(uri + 7);
1728 p = strchr(tmp, '/');
1732 if (!strcmp(tmp, "localhost"))
1733 path = uri + strlen("file://localhost");
1738 ret = gethostname(hostname, PATH_MAX);
1739 if ((ret == 0) && !strcmp(tmp, hostname))
1740 path = uri + strlen("file://") + strlen(hostname);
1746 /* uri:///foo/bar => absolute path /foo/bar on local machine */
1748 path = uri + strlen("file://");
1754 efreet_desktop_command_file_free(Efreet_Desktop_Command_File *file)
1758 IF_FREE(file->fullpath);
1761 IF_FREE(file->file);
1768 efreet_desktop_cb_download_complete(void *data, const char *file __UNUSED__,
1769 int status __UNUSED__)
1771 Efreet_Desktop_Command_File *f;
1775 /* XXX check status... error handling, etc */
1777 f->command->num_pending--;
1779 if (f->command->num_pending <= 0)
1782 execs = efreet_desktop_command_build(f->command);
1783 /* TODO: Need to handle the return value from efreet_desktop_command_execs_process */
1784 efreet_desktop_command_execs_process(f->command, execs);
1785 eina_list_free(execs);
1786 efreet_desktop_command_free(f->command);
1791 efreet_desktop_cb_download_progress(void *data,
1792 const char *file __UNUSED__,
1793 long int dltotal, long int dlnow,
1794 long int ultotal __UNUSED__,
1795 long int ulnow __UNUSED__)
1797 Efreet_Desktop_Command_File *dcf;
1800 if (dcf->command->cb_progress)
1801 return dcf->command->cb_progress(dcf->command->data,
1802 dcf->command->desktop,
1803 dcf->uri, dltotal, dlnow);
1809 * @brief Build an absolute path from an absolute or relative one.
1810 * @param path: an absolute or relative path
1811 * @return an allocated absolute path (must be freed)
1814 efreet_desktop_command_path_absolute(const char *path)
1817 int size = PATH_MAX;
1823 if (!(buf = malloc(size))) return NULL;
1824 if (!getcwd(buf, size))
1831 if (buf[len-1] != '/') buf = efreet_string_append(buf, &size, &len, "/");
1832 buf = efreet_string_append(buf, &size, &len, path);
1837 /* just dup an already absolute buffer */
1838 return strdup(path);