Tizen 2.1 base
[framework/uifw/ecore.git] / src / lib / ecore_desktop / ecore_desktop_icon.c
1 /*
2  * vim:ts=8:sw=3:sts=8:noexpandtab:cino=>5n-3f0^-2{2
3  */
4 #include <limits.h>
5 #include <sys/stat.h>
6
7 #include "Ecore_Desktop.h"
8 #include "ecore_desktop_private.h"
9 #include "ecore_private.h"
10
11 //#define DEBUG 1
12
13 static char        *_ecore_desktop_icon_find0(const char *icon,
14                                               const char *icon_size,
15                                               const char *icon_theme, 
16                                               int *in_cache);
17
18 static int          _ecore_desktop_icon_theme_list_add(void *data,
19                                                        const char *path);
20 static void         _ecore_desktop_icon_theme_destroy(Ecore_Desktop_Icon_Theme *
21                                                       icon_theme);
22 static void        
23 _ecore_desktop_icon_theme_directory_destroy(Ecore_Desktop_Icon_Theme_Directory *
24                                             icon_theme_directory);
25 static inline void
26 _ecore_desktop_icon_theme_cache_check(Ecore_Desktop_Icon_Theme *icon_theme);
27
28 /* FIXME: We need a way for the client to disable searching for any of these that they don't support. */
29 static const char  *ext[] =
30    { "", ".edj", ".png", ".svgz", ".svg", ".xpm", NULL };  /* "" is in case the icon already has an extension, search for that first. */
31 static int          init_count = 0;
32 static Ecore_Hash  *icon_theme_cache = NULL;
33
34 /**
35  * @defgroup Ecore_Desktop_Icon_Group icon theme Functions
36  *
37  * Functions that deal with freedesktop.org icon themes.
38  *
39  * This conforms with the freedesktop.org XDG Icon Theme Specification version 0.11
40  */
41
42 /**
43  * Find the path to an icon.
44  *
45  * Using the search algorithm specified by freedesktop.org,
46  * search for an icon in the currently installed set of icon themes.
47  *
48  * The returned string needs to be freed eventually.
49  *
50  * @param   icon The name of the required icon.
51  * @param   icon_size The size of the required icon.
52  * @param   icon_theme The theme of the required icon.
53  * @return  The full path to an icon file, or NULL.
54  * @ingroup Ecore_Desktop_Icon_Group
55  */
56
57 EAPI char         *
58 ecore_desktop_icon_find(const char *icon, const char *icon_size,
59                         const char *icon_theme)
60 {
61    char           *result = NULL, *icn;
62    Ecore_List     *icons;
63    int             in_cache = 0;
64    double          begin;
65
66    begin = ecore_time_get();
67    if (icon)
68      {
69         /* Easy check first, was a full path supplied? */
70         if ((icon[0] == '/') && (ecore_file_exists(icon)))
71            result = strdup(icon);
72         else
73           {
74              icons = ecore_desktop_paths_to_list(icon);
75              if (icons)
76                {
77
78                   if (icon_size == NULL)
79                      icon_size = "48x48";
80                   if (icon_theme == NULL)
81                      icon_theme = "hicolor";
82                   ecore_list_first_goto(icons);
83                   while ((icn = ecore_list_next(icons)))
84                     {
85                        char *ext;
86 #ifdef DEBUG
87                        fprintf(stderr, "\tTrying To Find Icon %s\n", icn);
88 #endif
89                        ext = strrchr(icn, '.');
90                        /* Check for unsupported extension */
91                        if ((ext) && (!strcmp(ext, ".ico")))
92                           continue;
93
94                        result = _ecore_desktop_icon_find0(icn, icon_size, icon_theme, &in_cache);
95                        if (result)
96                           break;
97                     }
98                   ecore_list_destroy(icons);
99
100                } /* if (icons) */
101           } /* if ((icon[0] == '/') && (ecore_file_exists(icon))) ; else */
102      } /* if (icon) */
103
104    if (result)
105      {
106         if (in_cache)
107           {
108              instrumentation.icons_in_cache_time += ecore_time_get() - begin;
109              instrumentation.icons_in_cache++;
110           }
111         else
112           {
113              instrumentation.icons_time += ecore_time_get() - begin;
114              instrumentation.icons++;
115           }
116      }
117    else
118      {
119         instrumentation.icons_not_found_time += ecore_time_get() - begin;
120         instrumentation.icons_not_found++;
121      }
122
123    return result;
124 }
125
126 /** Search for an icon the fdo way.
127  *
128  * This complies with the freedesktop.org Icon Theme Specification version 0.7
129  *
130  * @param   icon The icon to search for.
131  * @param   icon_size The icon size to search for.
132  * @param   icon_theme The icon theme to search in.
133  * @return  The full path to the found icon.
134  */
135 static char  *
136 _ecore_desktop_icon_find0(const char *icon, const char *icon_size,
137                           const char *icon_theme, int *in_cache)
138 {
139    Ecore_Desktop_Icon_Theme *theme;
140    char                path[PATH_MAX];
141    char               *found = NULL;
142    int                 wanted_size;
143    int                 minimal_size = INT_MAX;
144    int                 has_ext = 0;
145    int                 has_icon_ext = 0;
146    int                 i;
147    char               *closest = NULL;
148    Ecore_Desktop_Icon_Theme_Directory *directory;
149
150    if ((icon == NULL) || (icon[0] == '\0'))
151       return NULL;
152
153    /* Check the file extension, if any. */
154    found = strrchr(icon, '.');
155    if (found != NULL)
156      {
157         has_ext = 1;
158         for (i = 0; ext[i] != NULL; i++)
159           {
160              if (strcmp(found, ext[i]) == 0)
161                {
162                   has_icon_ext = 1;
163                   break;
164                }
165           }
166         found = NULL;
167      }
168
169 #ifdef DEBUG
170    fprintf(stderr, "\tTrying To Find Icon %s (%s) in theme %s\n", icon,
171            icon_size, icon_theme);
172 #endif
173
174    /* Get the theme description file. */
175    theme = ecore_desktop_icon_theme_get(icon_theme, NULL);
176 #ifdef DEBUG
177    printf("SEARCHING FOR %s\n", icon_theme);
178 #endif
179
180    if (!theme) return NULL;
181    if (!theme->Directories) goto done;
182
183    wanted_size = atoi(icon_size);
184
185    /* Loop through the themes directories. */
186    ecore_list_first_goto(theme->Directories);
187    while ((directory = ecore_list_next(theme->Directories)) != NULL)
188      {
189         if (directory->size)
190           {
191              int                 match = 0;
192              int                 result_size = 0;
193
194              /* Does this theme directory match the required icon size? */
195              switch (directory->type[0])
196                {
197                 case 'F':       /* Fixed. */
198                    match = (wanted_size == directory->size);
199                    result_size = abs(directory->size - wanted_size);
200                    break;
201                 case 'S':       /* Scaled. */
202                    match = ((directory->minimum <= wanted_size) &&
203                             (wanted_size <= directory->maximum));
204                    if (wanted_size < directory->minimum)
205                      result_size = directory->minimum - wanted_size;
206                    if (wanted_size > directory->maximum)
207                      result_size = wanted_size - directory->maximum;
208                    break;
209                 default:        /* Threshold. */
210                    match = (((directory->size - directory->threshold) <= wanted_size) &&
211                              (wanted_size <= (directory->size + directory->threshold)));
212                    if (wanted_size < (directory->size - directory->threshold))
213                      result_size = directory->minimum - wanted_size;
214                    if (wanted_size > (directory->size + directory->threshold))
215                      result_size = wanted_size - directory->maximum;
216                    break;
217                }
218
219              /* Do we need to check this directory? */
220              if ((match) || (result_size < minimal_size))
221                {
222                   /* Look for icon with all extensions. */
223                   for (i = 0; ext[i] != NULL; i++)
224                     {
225                        /* Check if there will be an extension to check. */
226                        if ((ext[i][0] == '\0') && (!has_ext))
227                           continue;
228                        if ((ext[i][0] != '\0') && (has_icon_ext))
229                           continue;
230                        if (directory->icons)
231                          {
232                             snprintf(path, PATH_MAX, "%s%s", icon, ext[i]);
233 #ifdef DEBUG
234                             printf("FDO icon = %s\n", path);
235 #endif
236                             found = ecore_hash_get(directory->icons, path);
237                             if (found)
238                               {
239                                  found = strdup(found);
240                                  if (match)
241                                     *in_cache = 1;
242                               }
243                          }
244                        else
245                          {
246                             snprintf(path, PATH_MAX, "%s/%s%s", directory->full_path, icon, ext[i]);
247 #ifdef DEBUG
248                             printf("FDO icon = %s\n", path);
249 #endif
250                             if (ecore_file_exists(path))
251                                found = strdup(path);
252                          }
253                        if (found)
254                          {
255                             if (ecore_file_is_dir(found))
256                               {
257                                  free(found);
258                                  found = NULL;
259                               }
260                             else if (match)     /* If there is a match in sizes, return the icon. */
261                                goto done;
262                             else if (result_size < minimal_size)        /* While we are here, figure out our next fallback strategy. */
263                               {
264                                  minimal_size = result_size;
265                                  if (closest) free(closest);
266                                  closest = found;
267                                  found = NULL;
268                               }
269                             else
270                               {
271                                  free(found);
272                                  found = NULL;
273                               }
274                          }
275                     }   /* for (i = 0; ext[i] != NULL; i++) */
276                }   /* if ((match) || (result_size < minimal_size)) */
277           }   /* if (directory->size) */
278      }   /* while ((directory = ecore_list_next(directory_paths)) != NULL) */
279
280    if (!found)
281      {
282         /* Fall back strategy #1, look for closest size in this theme. */
283         found = closest;
284         if (found)
285           {
286             closest = NULL;
287             goto done;
288           }
289
290         /* Fall back strategy #2, Try again with the parent themes. */
291         if (!theme->hicolor)
292           {
293              if (theme->Inherits)
294                {
295                   char *inherits;
296
297                   ecore_list_first_goto(theme->Inherits);
298                   while ((inherits = ecore_list_next(theme->Inherits)) != NULL)
299                     {
300                        found = _ecore_desktop_icon_find0(icon, icon_size, inherits, in_cache);
301                        if (found) goto done;
302                     }
303                }
304              else   /* Fall back strategy #3, Try the default hicolor theme. */
305                {
306                   found = _ecore_desktop_icon_find0(icon, icon_size, "hicolor", in_cache);
307                   if (found) goto done;
308                }
309           }
310
311         /* Fall back strategy #4, Just search in the base of the icon directories. */
312         for (i = 0; ext[i] != NULL; i++)
313           {
314              /* Check if there will be an extension to check. */
315              if ((ext[i][0] == '\0') && (!has_ext))
316                 continue;
317              if ((ext[i][0] != '\0') && (has_icon_ext))
318                 continue;
319              snprintf(path, PATH_MAX, "%s%s", icon, ext[i]);
320 #ifdef DEBUG
321              printf("FDO icon = %s\n", path);
322 #endif
323              found = ecore_desktop_paths_file_find(ecore_desktop_paths_icons, path, 0, NULL, NULL);
324              if (found)
325                {
326                   if (ecore_file_is_dir(found))
327                     {
328                        free(found);
329                        found = NULL;
330                     }
331                   else
332                     goto done;
333                }
334           }
335      }
336
337 done:
338    if (closest) free(closest);
339    ecore_desktop_icon_theme_destroy(theme);
340
341    return found;
342 }
343
344 Ecore_Hash         *
345 ecore_desktop_icon_theme_list(void)
346 {
347    static int          loaded = 0;
348    if (!loaded)
349      {
350         char *tmp;
351         tmp = ecore_desktop_paths_file_find(ecore_desktop_paths_icons, "index.theme", 2,
352                                             _ecore_desktop_icon_theme_list_add, NULL);
353         loaded = 1;
354         free(tmp);
355      }
356    return icon_theme_cache;
357 }
358
359 static int
360 _ecore_desktop_icon_theme_list_add(void *data __UNUSED__, const char *path)
361 {
362    char                icn[PATH_MAX];
363
364    snprintf(icn, PATH_MAX, "%sindex.theme", path);
365    if (ecore_desktop_icon_theme_get(icn, NULL))
366       return 1;                 /* Should stop it from recursing this directory, but let it continue searching the next. */
367    return 0;
368 }
369
370 /**
371  * Setup what ever needs to be setup to support ecore_desktop_icon.
372  *
373  * There are internal structures that are needed for ecore_desktop_icon
374  * functions to operate, this sets them up.
375  *
376  * @ingroup Ecore_Desktop_Icon_Group
377  */
378 EAPI int
379 ecore_desktop_icon_init()
380 {
381    if (++init_count != 1)
382       return init_count;
383
384    if (!icon_theme_cache)
385      {
386         icon_theme_cache = ecore_hash_new(ecore_str_hash, ecore_str_compare);
387         if (icon_theme_cache)
388           {
389              ecore_hash_free_key_cb_set(icon_theme_cache, free);
390              ecore_hash_free_value_cb_set(icon_theme_cache,
391                                        ECORE_FREE_CB(_ecore_desktop_icon_theme_destroy));
392           }
393      }
394
395    return init_count;
396 }
397
398 /**
399  * Tear down what ever needs to be torn down to support ecore_desktop_ycon.
400  *
401  * There are internal structures that are needed for ecore_desktop_icon
402  * functions to operate, this tears them down.
403  *
404  * @ingroup Ecore_Desktop_Icon_Group
405  */
406 EAPI int
407 ecore_desktop_icon_shutdown()
408 {
409    if (--init_count != 0)
410       return init_count;
411
412    if (icon_theme_cache)
413      {
414         ecore_hash_destroy(icon_theme_cache);
415         icon_theme_cache = NULL;
416      }
417
418    return init_count;
419 }
420
421 /**
422  * Get the contents of an index.theme file.
423  *
424  * Everything that is in the index.theme file is returned in the
425  * data member of the Ecore_Desktop_Icon_Theme structure, it's an Ecore_Hash 
426  * as returned by ecore_desktop_ini_get().  Some of the data in the
427  * index.theme file is decoded into specific members of the returned 
428  * structure.
429  *
430  * Use ecore_desktop_icon_theme_destroy() to free this structure.
431  * 
432  * @param   icon_theme Name of the icon theme, or full path to the index.theme file.
433  * @param   lang Language to use, or NULL for default.
434  * @return  An Ecore_Desktop_Icon_Theme containing the files contents.
435  * @ingroup Ecore_Desktop_Icon_Group
436  */
437 Ecore_Desktop_Icon_Theme *
438 ecore_desktop_icon_theme_get(const char *icon_theme, const char *lang __UNUSED__)
439 {
440    Ecore_Desktop_Icon_Theme *result = NULL;
441    char *theme_path = NULL, *theme_dir = NULL;
442    const char         *value;
443    Ecore_List         *Directories;
444    char               *directory;
445
446    if (icon_theme[0] == '/')
447       {
448          theme_path = strdup(icon_theme);
449          theme_dir = ecore_file_dir_get(theme_path);
450          if (theme_dir)
451             icon_theme = ecore_file_file_get(theme_dir);
452 #ifdef DEBUG
453          printf("LOADING THEME %s  -   %s\n", icon_theme, theme_path);
454 #endif
455       }
456
457    result = ecore_hash_get(icon_theme_cache, icon_theme);
458    if (result) goto done;
459    if (!theme_dir)
460      {
461         char icn[PATH_MAX];
462
463         snprintf(icn, PATH_MAX, "%s/index.theme", icon_theme);
464 #ifdef DEBUG
465         printf("SEARCHING FOR %s\n", icn);
466 #endif
467         theme_path = ecore_desktop_paths_file_find(ecore_desktop_paths_icons, icn,
468                                                    2, NULL, NULL);
469         if (!theme_path)  goto error;
470         theme_dir = ecore_file_dir_get(theme_path);
471      }
472    if (!theme_path) goto error;
473    result = calloc(1, sizeof(Ecore_Desktop_Icon_Theme));
474    if (!result) goto error;
475    result->data = ecore_desktop_ini_get(theme_path);
476    if (!result->data) goto error;
477    result->group = ecore_hash_get(result->data, "Icon Theme");
478    if (!result->group) goto error;
479
480
481    if ((strcmp(icon_theme, "hicolor") == 0))
482       result->hicolor = 1;
483
484    /* According to the spec, name and comment are required, but we can fake those easily enough. */
485    value = ecore_hash_get(result->group, "Name");
486    if (!value) value = icon_theme;
487    result->name = strdup(value);
488    value = ecore_hash_get(result->group, "Comment");
489    if (!value) value = "No comment provided.";
490    result->comment = strdup(value);
491    value = ecore_hash_get(result->group, "Inherits");
492    if (value)
493      {
494         result->inherits = strdup(value);
495         if (result->inherits)
496           result->Inherits = ecore_desktop_paths_to_list(result->inherits);
497      }
498    value = ecore_hash_get(result->group, "Example");
499    if (!value) value = "exec";
500    result->example = strdup(value);
501    value = ecore_hash_get(result->group, "Directories");
502    /* FIXME: Directories is also required, don't feel like faking it for now. */
503    if (!value) goto error;
504    result->directories = strdup(value);
505    Directories = ecore_desktop_paths_to_list(result->directories);
506    if (!Directories) goto error;
507    result->Directories = ecore_list_new();
508    if (!result->Directories) goto error;
509    ecore_list_free_cb_set(result->Directories,
510                           ECORE_FREE_CB(_ecore_desktop_icon_theme_directory_destroy));
511    ecore_list_first_goto(Directories);
512    while ((directory = ecore_list_next(Directories)) != NULL)
513      {
514         Ecore_Hash         *sub_group;
515         Ecore_Desktop_Icon_Theme_Directory *dir;
516
517         /* Get the details for this theme directory. */
518         sub_group = ecore_hash_get(result->data, directory);
519         dir = calloc(1, sizeof (Ecore_Desktop_Icon_Theme_Directory));
520         if ((dir) && (sub_group))
521           {
522              const char *size, *minsize, *maxsize, *threshold;
523              char full_path[PATH_MAX];
524
525              dir->path = strdup(directory);
526              snprintf(full_path, PATH_MAX, "%s/%s", theme_dir, directory);
527              dir->full_path = strdup(full_path);
528              value = ecore_hash_get(sub_group, "Type");
529              if (!value)
530                value = "Threshold";
531              dir->type = strdup(value);
532              size = ecore_hash_get(sub_group, "Size");
533              minsize = ecore_hash_get(sub_group, "MinSize");
534              maxsize = ecore_hash_get(sub_group, "MaxSize");
535              threshold = ecore_hash_get(sub_group, "Threshold");
536              if (size)
537                {
538                   if (!minsize)
539                     minsize = size;
540                   if (!maxsize)
541                     maxsize = size;
542                   if (!threshold)
543                     threshold = "2";
544                   dir->minimum = atoi(minsize);
545                   dir->maximum = atoi(maxsize);
546                   dir->threshold = atoi(threshold);
547
548                   dir->size = atoi(size);
549                   ecore_list_append(result->Directories, dir);
550                }
551              else
552                _ecore_desktop_icon_theme_directory_destroy(dir);
553           }
554         else if (dir)
555           _ecore_desktop_icon_theme_directory_destroy(dir);
556      }
557    ecore_list_destroy(Directories);
558
559    /* This passes the basic validation tests, mark it as real and cache it. */
560    result->path = strdup(theme_path);
561    ecore_hash_set(icon_theme_cache, strdup(icon_theme), result);
562    ecore_hash_destroy(result->data);
563    result->data = NULL;
564    result->group = NULL;
565
566 done:
567    if (theme_dir)  free(theme_dir);
568    if (theme_path) free(theme_path);
569
570    /* Cache the directories. */
571    _ecore_desktop_icon_theme_cache_check(result);
572    return result;
573
574 error:
575    if (theme_dir)  free(theme_dir);
576    if (theme_path) free(theme_path);
577    if (result)
578      {
579         if (result->data) ecore_hash_destroy(result->data);
580         _ecore_desktop_icon_theme_destroy(result);
581      }
582    return NULL;
583 }
584
585 /**
586  * Free whatever resources are used by an Ecore_Desktop_Icon_Theme.
587  *
588  * There are internal resources used by each Ecore_Desktop_Icon_Theme
589  * This releases those resources.
590  *
591  * @param  icon_theme  An Ecore_Desktop_Icon_Theme.
592  * @ingroup Ecore_Desktop_Icon_Group
593  */
594 void
595 ecore_desktop_icon_theme_destroy(Ecore_Desktop_Icon_Theme * icon_theme)
596 {
597    /* This is just a dummy, because these structures are cached. */
598    /* Later versions of the cache may reference count, then this will be useful. */
599
600    icon_theme = NULL;
601 }
602
603 static void
604 _ecore_desktop_icon_theme_destroy(Ecore_Desktop_Icon_Theme * icon_theme)
605 {
606    if (icon_theme->path)
607       free(icon_theme->path);
608    if (icon_theme->name)
609       free(icon_theme->name);
610    if (icon_theme->comment)
611       free(icon_theme->comment);
612    if (icon_theme->example)
613       free(icon_theme->example);
614    if (icon_theme->inherits)
615       free(icon_theme->inherits);
616    if (icon_theme->directories)
617       free(icon_theme->directories);
618    if (icon_theme->Directories)
619       ecore_list_destroy(icon_theme->Directories);
620    if (icon_theme->Inherits)
621       ecore_list_destroy(icon_theme->Inherits);
622    free(icon_theme);
623 }
624
625 static void
626 _ecore_desktop_icon_theme_directory_destroy(Ecore_Desktop_Icon_Theme_Directory *
627                                             icon_theme_directory)
628 {
629    if (icon_theme_directory->path)
630       free(icon_theme_directory->path);
631    if (icon_theme_directory->full_path)
632       free(icon_theme_directory->full_path);
633    if (icon_theme_directory->type)
634       free(icon_theme_directory->type);
635    if (icon_theme_directory->icons)
636       ecore_hash_destroy(icon_theme_directory->icons);
637    free(icon_theme_directory);
638 }
639
640 static inline void
641 _ecore_desktop_icon_theme_cache_check(Ecore_Desktop_Icon_Theme *icon_theme)
642 {
643    /* The spec has this to say -
644     *
645     * "The algorithm as described in this document works by always looking up 
646     * filenames in directories (a stat in unix terminology). A good 
647     * implementation is expected to read the directories once, and do all 
648     * lookups in memory using that information.
649     *
650     * "This caching can make it impossible for users to add icons without having 
651     * to restart applications. In order to handle this, any implementation that 
652     * does caching is required to look at the mtime of the toplevel icon 
653     * directories when doing a cache lookup, unless it already did so less than 
654     * 5 seconds ago. This means that any icon editor or theme installation 
655     * program need only to change the mtime of the the toplevel directory where 
656     * it changed the theme to make sure that the new icons will eventually get 
657     * used."
658     *
659     * The phrase "toplevel icon directories" is ambigous, but I guess they mean 
660     * the directory where the index.theme file lives.
661     */
662
663    struct stat         st;
664    int                 clear = 0;
665
666    if (ecore_time_get() > (icon_theme->last_checked + 5.0))
667      {
668         if (stat(icon_theme->path, &st) >= 0)
669           {
670              icon_theme->last_checked = ecore_time_get();
671              if (st.st_mtime > icon_theme->mtime)
672                {
673                   clear = 1;
674                   icon_theme->mtime = st.st_mtime;
675                }
676           }
677      }
678
679    if (clear)
680      {
681         Ecore_Desktop_Icon_Theme_Directory *dir;
682         char full_path[PATH_MAX];
683
684         ecore_list_first_goto(icon_theme->Directories);
685         while ((dir = ecore_list_next(icon_theme->Directories)) != NULL)
686           {
687              if (dir->icons)
688                {
689                    ecore_hash_destroy(dir->icons);
690                    dir->icons = NULL;
691                }
692              dir->icons = ecore_hash_new(ecore_str_hash, ecore_str_compare);
693              if (dir->icons)
694                {
695                   Ecore_List *files;
696
697                   ecore_hash_free_key_cb_set(dir->icons, free);
698                   ecore_hash_free_value_cb_set(dir->icons, free);
699                   files = ecore_file_ls(dir->full_path);
700                   if (files)
701                     {
702                        const char *file;
703
704                        while ((file = ecore_list_next(files)))
705                          {
706                             snprintf(full_path, PATH_MAX, "%s/%s", dir->full_path, file);
707                             ecore_hash_set(dir->icons, strdup(file), strdup(full_path));
708                          }
709                        ecore_list_destroy(files);
710                     }
711                }
712           }
713      }
714 }