EFL 1.7 svn doobies
[profile/ivi/efreet.git] / src / lib / efreet_icon.c
1 #ifdef HAVE_CONFIG_H
2 # include <config.h>
3 #endif
4
5 #undef alloca
6 #ifdef HAVE_ALLOCA_H
7 # include <alloca.h>
8 #elif defined __GNUC__
9 # define alloca __builtin_alloca
10 #elif defined _AIX
11 # define alloca __alloca
12 #elif defined _MSC_VER
13 # include <malloc.h>
14 # define alloca _alloca
15 #else
16 # include <stddef.h>
17 # ifdef  __cplusplus
18 extern "C"
19 # endif
20 void *alloca (size_t);
21 #endif
22
23 #include <Ecore.h>
24 #include <Ecore_File.h>
25
26 /* define macros and variable for using the eina logging system  */
27 #define EFREET_MODULE_LOG_DOM _efreet_icon_log_dom
28 static int _efreet_icon_log_dom = -1;
29
30 #include "Efreet.h"
31 #include "efreet_private.h"
32
33 static const char *efreet_icon_deprecated_user_dir = NULL;
34 static const char *efreet_icon_user_dir = NULL;
35 static Eina_List *efreet_icon_extensions = NULL;
36 static Eina_List *efreet_extra_icon_dirs = NULL;
37
38 static Eina_Hash *change_monitors = NULL;
39
40 typedef struct Efreet_Icon_Cache Efreet_Icon_Cache;
41 struct Efreet_Icon_Cache
42 {
43     const char *key;
44     const char *path;
45     time_t lasttime;
46 };
47
48 static char *efreet_icon_remove_extension(const char *icon);
49
50 static Efreet_Icon *efreet_icon_new(const char *path);
51 static void efreet_icon_populate(Efreet_Icon *icon, const char *file);
52
53 static const char *efreet_icon_lookup_icon(Efreet_Cache_Icon *icon, unsigned int size);
54 static const char *efreet_icon_list_lookup_icon(Efreet_Icon_Theme *theme, Eina_List *icons, unsigned int size);
55 static int efreet_icon_size_match(Efreet_Cache_Icon_Element *elem, unsigned int size);
56 static double efreet_icon_size_distance(Efreet_Cache_Icon_Element *elem, unsigned int size);
57 static const char *efreet_icon_lookup_path(Efreet_Cache_Icon_Element *elem);
58 static const char *efreet_icon_lookup_path_path(Efreet_Cache_Icon_Element *elem, const char *path);
59 static const char *efreet_icon_fallback_lookup_path(Efreet_Cache_Fallback_Icon *icon);
60 static const char *efreet_icon_fallback_lookup_path_path(Efreet_Cache_Fallback_Icon *icon,
61                                                                const char *path);
62
63 static void efreet_icon_changes_listen(void);
64 static void efreet_icon_changes_monitor_add(const char *path);
65 static void efreet_icon_changes_cb(void *data, Ecore_File_Monitor *em,
66                                    Ecore_File_Event event, const char *path);
67
68
69 /**
70  * @internal
71  * @return Returns 1 on success or 0 on failure
72  * @brief Initializes the icon system
73  */
74 int
75 efreet_icon_init(void)
76 {
77     const char *default_exts[] = {".png", ".xpm", ".svg", NULL};
78     int i;
79
80     _efreet_icon_log_dom = eina_log_domain_register
81         ("efreet_icon", EFREET_DEFAULT_LOG_COLOR);
82     if (_efreet_icon_log_dom < 0)
83         return 0;
84
85     /* setup the default extension list */
86     for (i = 0; default_exts[i]; i++)
87         efreet_icon_extensions = eina_list_append(efreet_icon_extensions, eina_stringshare_add(default_exts[i]));
88
89     efreet_icon_changes_listen();
90
91     efreet_extra_icon_dirs = NULL;
92
93     return 1;
94 }
95
96 /**
97  * @internal
98  * @return Returns no value
99  * @brief Shuts down the icon system
100  */
101 void
102 efreet_icon_shutdown(void)
103 {
104     IF_RELEASE(efreet_icon_user_dir);
105     IF_RELEASE(efreet_icon_deprecated_user_dir);
106
107     IF_FREE_LIST(efreet_icon_extensions, eina_stringshare_del);
108     efreet_extra_icon_dirs = eina_list_free(efreet_extra_icon_dirs);
109
110     eina_log_domain_unregister(_efreet_icon_log_dom);
111     _efreet_icon_log_dom = -1;
112     IF_FREE_HASH(change_monitors);
113 }
114
115 EAPI const char *
116 efreet_icon_deprecated_user_dir_get(void)
117 {
118     const char *user;
119     char *tmp;
120     int len;
121
122     if (efreet_icon_deprecated_user_dir) return efreet_icon_deprecated_user_dir;
123
124     user = efreet_home_dir_get();
125     len = strlen(user) + strlen("/.icons") + 1;
126     tmp = alloca(len);
127     snprintf(tmp, len, "%s/.icons", user);
128
129     efreet_icon_deprecated_user_dir = eina_stringshare_add_length(tmp, len - 1);
130
131     return efreet_icon_deprecated_user_dir;
132 }
133
134 EAPI const char *
135 efreet_icon_user_dir_get(void)
136 {
137     const char *user;
138     char *tmp;
139     int len;
140
141     if (efreet_icon_user_dir) return efreet_icon_user_dir;
142
143     user = efreet_data_home_get();
144     len = strlen(user) + strlen("/icons") + 1;
145     tmp = alloca(len);
146     snprintf(tmp, len, "%s/icons", user);
147
148     efreet_icon_user_dir = eina_stringshare_add_length(tmp, len - 1);
149
150     return efreet_icon_user_dir;
151 }
152
153 EAPI void
154 efreet_icon_extension_add(const char *ext)
155 {
156     Eina_List *l;
157
158     EINA_SAFETY_ON_NULL_RETURN(ext);
159
160     ext = eina_stringshare_add(ext);
161
162     if ((l = eina_list_data_find_list(efreet_icon_extensions, ext)))
163     {
164         efreet_icon_extensions = eina_list_promote_list(efreet_icon_extensions, l);
165         eina_stringshare_del(ext);
166     }
167     else
168         efreet_icon_extensions = eina_list_prepend(efreet_icon_extensions, ext);
169 }
170
171 EAPI Eina_List **
172 efreet_icon_extra_list_get(void)
173 {
174     return &efreet_extra_icon_dirs;
175 }
176
177 EAPI Eina_List *
178 efreet_icon_extensions_list_get(void)
179 {
180     return efreet_icon_extensions;
181 }
182
183 EAPI Eina_List *
184 efreet_icon_theme_list_get(void)
185 {
186     return efreet_cache_icon_theme_list();
187 }
188
189 EAPI Efreet_Icon_Theme *
190 efreet_icon_theme_find(const char *theme_name)
191 {
192     if (!theme_name) return NULL;
193
194     return efreet_cache_icon_theme_find(theme_name);
195 }
196
197 /**
198  * @internal
199  * @param icon The icon name to strip extension
200  * @return Extension removed if in list of extensions, else untouched.
201  * @brief Removes extension from icon name if in list of extensions.
202  */
203 static char *
204 efreet_icon_remove_extension(const char *icon)
205 {
206     Eina_List *l;
207     char *tmp = NULL, *ext = NULL;
208
209     if (!icon) return NULL;
210
211     tmp = strdup(icon);
212     ext = strrchr(tmp, '.');
213     if (ext)
214     {
215         const char *ext2;
216         EINA_LIST_FOREACH(efreet_icon_extensions, l, ext2)
217         {
218             if (!strcmp(ext, ext2))
219             {
220 #ifdef STRICT_SPEC
221                 WRN("[Efreet]: Requesting an icon with an extension: %s",
222                     icon);
223 #endif
224                 *ext = '\0';
225                 break;
226             }
227         }
228     }
229
230     return tmp;
231 }
232
233 EAPI const char *
234 efreet_icon_path_find(const char *theme_name, const char *icon, unsigned int size)
235 {
236     char *tmp;
237     const char *value = NULL;
238     Efreet_Icon_Theme *theme;
239
240     EINA_SAFETY_ON_NULL_RETURN_VAL(icon, NULL);
241
242     theme = efreet_icon_theme_find(theme_name);
243
244 #ifdef SLOPPY_SPEC
245     tmp = efreet_icon_remove_extension(icon);
246     if (!tmp) return NULL;
247 #else
248     tmp = icon;
249 #endif
250
251     if (theme)
252     {
253         Efreet_Cache_Icon *cache;
254         cache = efreet_cache_icon_find(theme, tmp);
255         value = efreet_icon_lookup_icon(cache, size);
256         if (!value) INF("lookup for `%s` failed in theme `%s` with %p.", icon, theme_name, cache);
257     }
258
259     /* we didn't find the icon in the theme or in the inherited directories
260      * then just look for a non theme icon
261      */
262     if (!value)
263     {
264         Efreet_Cache_Fallback_Icon *cache;
265
266         cache = efreet_cache_icon_fallback_find(tmp);
267         value = efreet_icon_fallback_lookup_path(cache);
268         if (!value) INF("lookup for `%s` failed in fallback too with %p.", icon, cache);
269     }
270
271 #ifdef SLOPPY_SPEC
272     FREE(tmp);
273 #endif
274     return value;
275 }
276
277 EAPI const char *
278 efreet_icon_list_find(const char *theme_name, Eina_List *icons,
279                       unsigned int size)
280 {
281     Eina_List *l;
282     Eina_List *tmps = NULL;
283     const char *icon = NULL;
284     const char *value = NULL;
285     char *data;
286     Efreet_Icon_Theme *theme;
287
288     EINA_SAFETY_ON_NULL_RETURN_VAL(icons, NULL);
289
290     theme = efreet_icon_theme_find(theme_name);
291
292 #ifdef SLOPPY_SPEC
293     EINA_LIST_FOREACH(icons, l, icon)
294     {
295         data = efreet_icon_remove_extension(icon);
296         if (!data) return NULL;
297         tmps = eina_list_append(tmps, data);
298     }
299 #else
300     tmps = icons;
301 #endif
302
303     if (theme)
304     {
305         Eina_List *tmps2 = NULL;
306         Efreet_Cache_Icon *cache;
307
308         EINA_LIST_FOREACH(tmps, l, icon)
309         {
310             cache = efreet_cache_icon_find(theme, icon);
311             if (cache)
312             {
313                 /* If the icon is in the asked for theme, return it */
314                 if (!strcmp(cache->theme, theme->name.internal))
315                 {
316                     value = efreet_icon_lookup_icon(cache, size);
317                     break;
318                 }
319                 else
320                     tmps2 = eina_list_append(tmps2, cache);
321             }
322         }
323         if (tmps2)
324         {
325             if (!value)
326                 value = efreet_icon_list_lookup_icon(theme, tmps2, size);
327             eina_list_free(tmps2);
328         }
329     }
330
331     /* we didn't find the icons in the theme or in the inherited directories
332      * then just look for a non theme icon
333      */
334     if (!value)
335     {
336         Efreet_Cache_Fallback_Icon *cache;
337         EINA_LIST_FOREACH(tmps, l, icon)
338         {
339             cache = efreet_cache_icon_fallback_find(icon);
340             value = efreet_icon_fallback_lookup_path(cache);
341             if (value)
342                 break;
343         }
344     }
345
346 #ifdef SLOPPY_SPEC
347     EINA_LIST_FREE(tmps, data)
348         free(data);
349 #endif
350
351     return value;
352 }
353
354 EAPI Efreet_Icon *
355 efreet_icon_find(const char *theme_name, const char *icon, unsigned int size)
356 {
357     const char *path;
358
359     EINA_SAFETY_ON_NULL_RETURN_VAL(icon, NULL);
360
361     path = efreet_icon_path_find(theme_name, icon, size);
362     if (path)
363     {
364         Efreet_Icon *ic;
365
366         ic = efreet_icon_new(path);
367         return ic;
368     }
369
370     return NULL;
371 }
372
373 /**
374  * @internal
375  * @return Returns a new Efreet_Icon struct on success or NULL on failure
376  * @brief Creates a new Efreet_Icon struct
377  */
378 static Efreet_Icon *
379 efreet_icon_new(const char *path)
380 {
381     Efreet_Icon *icon;
382     char *p;
383
384     icon = NEW(Efreet_Icon, 1);
385     if (!icon) return NULL;
386     icon->path = eina_stringshare_add(path);
387
388     /* load the .icon file if it's available */
389     p = strrchr(icon->path, '.');
390     if (p)
391     {
392         char ico_path[PATH_MAX];
393
394         *p = '\0';
395
396         snprintf(ico_path, sizeof(ico_path), "%s.icon", icon->path);
397         *p = '.';
398
399         if (ecore_file_exists(ico_path))
400             efreet_icon_populate(icon, ico_path);
401     }
402
403     if (!icon->name)
404     {
405         const char *file;
406
407         file = ecore_file_file_get(icon->path);
408         p = strrchr(icon->path, '.');
409         if (p) *p = '\0';
410         icon->name = eina_stringshare_add(file);
411         if (p) *p = '.';
412     }
413
414     return icon;
415 }
416
417 EAPI void
418 efreet_icon_free(Efreet_Icon *icon)
419 {
420     if (!icon) return;
421
422     icon->ref_count --;
423     if (icon->ref_count > 0) return;
424
425     IF_RELEASE(icon->path);
426     IF_RELEASE(icon->name);
427     IF_FREE_LIST(icon->attach_points, free);
428
429     FREE(icon);
430 }
431
432 /**
433  * @internal
434  * @param icon The icon to populate
435  * @param file The file to populate from
436  * @return Returns no value
437  * @brief Tries to populate the icon information from the given file
438  */
439 static void
440 efreet_icon_populate(Efreet_Icon *icon, const char *file)
441 {
442     Efreet_Ini *ini;
443     const char *tmp;
444
445     ini = efreet_ini_new(file);
446     if (!ini) return;
447     if (!ini->data)
448     {
449         efreet_ini_free(ini);
450         return;
451     }
452
453     efreet_ini_section_set(ini, "Icon Data");
454     tmp = efreet_ini_localestring_get(ini, "DisplayName");
455     if (tmp) icon->name = eina_stringshare_add(tmp);
456
457     tmp = efreet_ini_string_get(ini, "EmbeddedTextRectangle");
458     if (tmp)
459     {
460         int points[4];
461         char *t, *s, *p;
462         int i;
463         size_t len;
464
465         len = strlen(tmp) + 1;
466         t = alloca(len);
467         memcpy(t, tmp, len);
468         s = t;
469         for (i = 0; i < 4; i++)
470         {
471             if (s)
472             {
473                 p = strchr(s, ',');
474
475                 if (p) *p = '\0';
476                 points[i] = atoi(s);
477
478                 if (p) s = ++p;
479                 else s = NULL;
480             }
481             else
482             {
483                 points[i] = 0;
484             }
485         }
486
487         icon->has_embedded_text_rectangle = 1;
488         icon->embedded_text_rectangle.x0 = points[0];
489         icon->embedded_text_rectangle.y0 = points[1];
490         icon->embedded_text_rectangle.x1 = points[2];
491         icon->embedded_text_rectangle.y1 = points[3];
492     }
493
494     tmp = efreet_ini_string_get(ini, "AttachPoints");
495     if (tmp)
496     {
497         char *t, *s, *p;
498         size_t len;
499
500         len = strlen(tmp) + 1;
501         t = alloca(len);
502         memcpy(t, tmp, len);
503         s = t;
504         while (s)
505         {
506             Efreet_Icon_Point *point;
507
508             p = strchr(s, ',');
509             /* If this happens there is something wrong with the .icon file */
510             if (!p) break;
511
512             point = NEW(Efreet_Icon_Point, 1);
513             if (!point) goto error;
514
515             *p = '\0';
516             point->x = atoi(s);
517
518             s = ++p;
519             p = strchr(s, '|');
520             if (p) *p = '\0';
521
522             point->y = atoi(s);
523
524             icon->attach_points = eina_list_append(icon->attach_points, point);
525
526             if (p) s = ++p;
527             else s = NULL;
528         }
529     }
530
531 error:
532     efreet_ini_free(ini);
533 }
534
535 static const char *
536 efreet_icon_lookup_icon(Efreet_Cache_Icon *icon, unsigned int size)
537 {
538     const char *path = NULL;
539     double minimal_distance = INT_MAX;
540     unsigned int ret_size = 0;
541     unsigned int i;
542
543     if (!icon) return NULL;
544
545     /* search for allowed size == requested size */
546     for (i = 0; i < icon->icons_count; ++i)
547     {
548         if (!efreet_icon_size_match(icon->icons[i], size)) continue;
549         path = efreet_icon_lookup_path(icon->icons[i]);
550         if (path) return path;
551     }
552
553     /* search for any icon that matches */
554     for (i = 0; i < icon->icons_count; ++i)
555     {
556         const char *tmp = NULL;
557         double distance;
558
559         distance = efreet_icon_size_distance(icon->icons[i], size);
560         if (distance > minimal_distance) continue;
561         // prefer downsizing
562         if ((distance == minimal_distance) && (icon->icons[i]->normal < ret_size)) continue;
563
564         tmp = efreet_icon_lookup_path(icon->icons[i]);
565
566         if (tmp)
567         {
568             path = tmp;
569             minimal_distance = distance;
570             ret_size = icon->icons[i]->normal;
571         }
572     }
573
574     return path;
575 }
576
577 static const char *
578 efreet_icon_list_lookup_icon(Efreet_Icon_Theme *theme, Eina_List *icons, unsigned int size)
579 {
580     const char *value = NULL;
581     Efreet_Cache_Icon *cache;
582     Eina_List *l;
583
584     EINA_LIST_FOREACH(icons, l, cache)
585     {
586         if (!strcmp(cache->theme, theme->name.internal))
587         {
588             value = efreet_icon_lookup_icon(cache, size);
589             if (value) break;
590         }
591     }
592     if (value) return value;
593     if (theme->inherits)
594     {
595         const char *parent;
596         EINA_LIST_FOREACH(theme->inherits, l, parent)
597         {
598             Efreet_Icon_Theme *parent_theme;
599
600             parent_theme = efreet_icon_theme_find(parent);
601             if ((!parent_theme) || (parent_theme == theme)) continue;
602
603             value = efreet_icon_list_lookup_icon(parent_theme, icons, size);
604             if (value) break;
605         }
606     }
607     /* if this isn't the hicolor theme, and we have no other fallbacks
608      * check hicolor */
609     else if (strcmp(theme->name.internal, "hicolor"))
610     {
611         Efreet_Icon_Theme *parent_theme;
612
613         parent_theme = efreet_icon_theme_find("hicolor");
614         if (parent_theme)
615             value = efreet_icon_list_lookup_icon(parent_theme, icons, size);
616     }
617     return value;
618 }
619
620 static int
621 efreet_icon_size_match(Efreet_Cache_Icon_Element *elem, unsigned int size)
622 {
623     if (elem->type == EFREET_ICON_SIZE_TYPE_FIXED)
624         return (elem->normal == size);
625
626     if ((elem->type == EFREET_ICON_SIZE_TYPE_SCALABLE) ||
627         (elem->type == EFREET_ICON_SIZE_TYPE_THRESHOLD))
628         return ((elem->min < size) && (size < elem->max));
629
630     return 0;
631 }
632
633 static double
634 efreet_icon_size_distance(Efreet_Cache_Icon_Element *elem, unsigned int size)
635 {
636     if (elem->type == EFREET_ICON_SIZE_TYPE_FIXED)
637         return (abs(elem->normal - size));
638
639     if ((elem->type == EFREET_ICON_SIZE_TYPE_SCALABLE) ||
640         (elem->type == EFREET_ICON_SIZE_TYPE_THRESHOLD))
641     {
642 #ifdef STRICT_SPEC
643         if (size < elem->min)
644             return (elem->min - size);
645         if (elem->max < size)
646             return (size - elem->max);
647 #else
648         if (size < elem->min)
649             return (elem->min / (double)size);
650         if (elem->max < size)
651             return (size / (double)elem->max);
652 #endif
653     }
654
655     return 0;
656 }
657
658 static const char *
659 efreet_icon_lookup_path(Efreet_Cache_Icon_Element *elem)
660 {
661     Eina_List *xdg_dirs, *l;
662     const char *path;
663     const char *dir;
664     char buf[PATH_MAX];
665
666     if (elem->paths_count == 1)
667     {
668         const char *pp, *ext;
669
670         pp = strrchr(elem->paths[0], '.');
671         if (!pp) return NULL;
672
673         EINA_LIST_FOREACH(efreet_icon_extensions, l, ext)
674             if (!strcmp(pp, ext))
675                 return elem->paths[0];
676         return NULL;
677     }
678
679     path = efreet_icon_lookup_path_path(elem, efreet_icon_deprecated_user_dir_get());
680     if (path) return path;
681
682     path = efreet_icon_lookup_path_path(elem, efreet_icon_user_dir_get());
683     if (path) return path;
684
685 #if 0
686     EINA_LIST_FOREACH(efreet_extra_icon_dirs, l, dir)
687     {
688         path = efreet_icon_lookup_path_path(elem, dir);
689         if (path) return path;
690     }
691 #endif
692
693     xdg_dirs = efreet_data_dirs_get();
694
695     EINA_LIST_FOREACH(xdg_dirs, l, dir)
696     {
697         snprintf(buf, sizeof(buf), "%s/icons", dir);
698
699         path = efreet_icon_lookup_path_path(elem, buf);
700         if (path) return path;
701     }
702
703     return NULL;
704 }
705
706 static const char *
707 efreet_icon_lookup_path_path(Efreet_Cache_Icon_Element *elem, const char *path)
708 {
709     Eina_List *ll;
710     const char *ext, *pp;
711     unsigned int i;
712     int len;
713
714     len = strlen(path);
715
716     for (i = 0; i < elem->paths_count; ++i)
717     {
718         if (strncmp(path, elem->paths[i], len)) continue;
719         pp = strrchr(elem->paths[i], '.');
720         if (!pp) continue;
721
722         EINA_LIST_FOREACH(efreet_icon_extensions, ll, ext)
723             if (!strcmp(pp, ext))
724                 return elem->paths[i];
725     }
726
727     return NULL;
728 }
729
730 static const char *
731 efreet_icon_fallback_lookup_path(Efreet_Cache_Fallback_Icon *icon)
732 {
733     const char *path;
734     Eina_List *xdg_dirs, *l;
735     const char *dir;
736     char buf[PATH_MAX];
737
738     if (!icon) return NULL;
739
740     if (icon->icons_count == 1)
741     {
742         const char *pp, *ext;
743
744         pp = strrchr(icon->icons[0], '.');
745         if (!pp) return NULL;
746
747         EINA_LIST_FOREACH(efreet_icon_extensions, l, ext)
748             if (!strcmp(pp, ext))
749                 return icon->icons[0];
750         return NULL;
751     }
752
753     path = efreet_icon_fallback_lookup_path_path(icon, efreet_icon_deprecated_user_dir_get());
754     if (path) return path;
755
756     path = efreet_icon_fallback_lookup_path_path(icon, efreet_icon_user_dir_get());
757     if (path) return path;
758
759     EINA_LIST_FOREACH(efreet_extra_icon_dirs, l, dir)
760     {
761         path = efreet_icon_fallback_lookup_path_path(icon, dir);
762         if (path) return path;
763     }
764
765     xdg_dirs = efreet_data_dirs_get();
766
767     EINA_LIST_FOREACH(xdg_dirs, l, dir)
768     {
769         snprintf(buf, sizeof(buf), "%s/icons", dir);
770
771         path = efreet_icon_fallback_lookup_path_path(icon, buf);
772         if (path) return path;
773     }
774
775 #ifndef STRICT_SPEC
776     EINA_LIST_FOREACH(xdg_dirs, l, dir)
777     {
778         snprintf(buf, sizeof(buf), "%s/pixmaps", dir);
779
780         path = efreet_icon_fallback_lookup_path_path(icon, buf);
781         if (path) return path;
782     }
783 #endif
784
785     path = efreet_icon_fallback_lookup_path_path(icon, "/usr/share/pixmaps");
786     if (path) return path;
787
788     return NULL;
789 }
790
791 static const char *
792 efreet_icon_fallback_lookup_path_path(Efreet_Cache_Fallback_Icon *icon, const char *path)
793 {
794     Eina_List *ll;
795     const char *ext, *pp;
796     unsigned int i;
797     int len;
798
799     len = strlen(path);
800
801     for (i = 0; i < icon->icons_count; ++i)
802     {
803         if (strncmp(path, icon->icons[i], len)) continue;
804
805         pp = strrchr(icon->icons[i], '.');
806         if (!pp) continue;
807
808         EINA_LIST_FOREACH(efreet_icon_extensions, ll, ext)
809             if (!strcmp(pp, ext))
810                 return icon->icons[i];
811     }
812
813     return NULL;
814 }
815
816 static void
817 efreet_icon_changes_listen(void)
818 {
819     Eina_List *l;
820     Eina_List *xdg_dirs;
821     char buf[PATH_MAX];
822     const char *dir;
823
824     if (!efreet_cache_update) return;
825
826     change_monitors = eina_hash_string_superfast_new(EINA_FREE_CB(ecore_file_monitor_del));
827     if (!change_monitors) return;
828
829     efreet_icon_changes_monitor_add(efreet_icon_deprecated_user_dir_get());
830     efreet_icon_changes_monitor_add(efreet_icon_user_dir_get());
831     EINA_LIST_FOREACH(efreet_extra_icon_dirs, l, dir)
832         efreet_icon_changes_monitor_add(dir);
833
834     xdg_dirs = efreet_data_dirs_get();
835     EINA_LIST_FOREACH(xdg_dirs, l, dir)
836     {
837         snprintf(buf, sizeof(buf), "%s/icons", dir);
838         efreet_icon_changes_monitor_add(buf);
839     }
840
841 #ifndef STRICT_SPEC
842     EINA_LIST_FOREACH(xdg_dirs, l, dir)
843     {
844         snprintf(buf, sizeof(buf), "%s/pixmaps", dir);
845         efreet_icon_changes_monitor_add(buf);
846     }
847 #endif
848
849     efreet_icon_changes_monitor_add("/usr/share/pixmaps");
850 }
851
852 static void
853 efreet_icon_changes_monitor_add(const char *path)
854 {
855     char rp[PATH_MAX];
856
857     if (!realpath(path, rp)) return;
858     if (!ecore_file_is_dir(rp)) return;
859     if (eina_hash_find(change_monitors, rp)) return;
860     eina_hash_add(change_monitors, rp,
861                   ecore_file_monitor_add(rp,
862                                          efreet_icon_changes_cb,
863                                          NULL));
864
865     if (ecore_file_is_dir(rp))
866     {
867         Eina_Iterator *it;
868         const char *ent;
869
870         it = eina_file_ls(rp);
871         if (!it) return;
872         EINA_ITERATOR_FOREACH(it, ent)
873         {
874             if (!realpath(ent, rp)) continue;
875             if (!ecore_file_is_dir(rp)) continue;
876             eina_hash_add(change_monitors, rp,
877                           ecore_file_monitor_add(rp,
878                                                  efreet_icon_changes_cb,
879                                                  NULL));
880         }
881         eina_iterator_free(it);
882     }
883 }
884
885 static void
886 efreet_icon_changes_cb(void *data __UNUSED__, Ecore_File_Monitor *em __UNUSED__,
887                        Ecore_File_Event event, const char *path)
888 {
889     /* TODO: If we get a stale symlink, we need to rerun cache creation */
890     switch (event)
891     {
892         case ECORE_FILE_EVENT_NONE:
893             /* noop */
894             break;
895
896         case ECORE_FILE_EVENT_CREATED_FILE:
897         case ECORE_FILE_EVENT_DELETED_FILE:
898         case ECORE_FILE_EVENT_MODIFIED:
899         case ECORE_FILE_EVENT_CLOSED:
900         case ECORE_FILE_EVENT_DELETED_DIRECTORY:
901         case ECORE_FILE_EVENT_CREATED_DIRECTORY:
902             efreet_cache_icon_update();
903             break;
904
905         case ECORE_FILE_EVENT_DELETED_SELF:
906             eina_hash_del_by_key(change_monitors, path);
907             efreet_cache_icon_update();
908             break;
909     }
910 }