From 082b16efe143acbf5115aa002f5a2346d88f4beb Mon Sep 17 00:00:00 2001 From: raster Date: Thu, 30 Aug 2012 09:54:57 +0000 Subject: [PATCH] EFL 1.7 svn doobies git-svn-id: svn+ssh://svn.enlightenment.org/var/svn/e/branches/efreet-1.7@75862 7cbeb6ba-43b4-40fd-8cce-4c39aea84d33 --- .gitignore | 152 ++ AUTHORS | 7 + COPYING | 25 + ChangeLog | 132 + INSTALL | 236 ++ Makefile.am | 139 ++ NEWS | 58 + NOTES | 48 + README | 38 + TODO | 24 + autogen.sh | 39 + configure.ac | 285 +++ doc/Doxyfile.in | 1692 +++++++++++++ doc/Makefile.am | 32 + doc/e.css | 218 ++ doc/foot.html | 19 + doc/head.html | 68 + doc/images/e.png | Bin 0 -> 3825 bytes doc/images/edoxy.css | 483 ++++ doc/images/foot_bg.png | Bin 0 -> 173 bytes doc/images/head_bg.png | Bin 0 -> 214 bytes doc/images/menu_bg.png | Bin 0 -> 192 bytes doc/images/menu_bg_current.png | Bin 0 -> 1200 bytes doc/images/menu_bg_hover.png | Bin 0 -> 3278 bytes doc/images/menu_bg_last.png | Bin 0 -> 637 bytes doc/images/menu_bg_unsel.png | Bin 0 -> 1596 bytes efreet-mime.pc.in | 11 + efreet-trash.pc.in | 11 + efreet.pc.in | 11 + efreet.spec.in | 69 + m4/ac_attribute.m4 | 47 + m4/ac_define_dir.m4 | 47 + m4/efl_compiler_flag.m4 | 57 + m4/efl_coverage.m4 | 62 + m4/efl_doxygen.m4 | 97 + m4/efl_path_max.m4 | 37 + m4/efl_tests.m4 | 43 + po/LINGUAS | 12 + po/Makevars | 41 + po/POTFILES.in | 1 + po/cs.po | 20 + po/de.po | 20 + po/efreet.pot | 22 + po/el.po | 20 + po/es.po | 20 + po/fr.po | 20 + po/it.po | 20 + po/ja.po | 20 + po/ko.po | 20 + po/nl.po | 20 + po/pt.po | 20 + po/ru.po | 20 + po/sl.po | 20 + src/Makefile.am | 4 + src/bin/Makefile.am | 29 + src/bin/efreet_desktop_cache_create.c | 541 ++++ src/bin/efreet_icon_cache_create.c | 1131 +++++++++ src/lib/Efreet.h | 107 + src/lib/Efreet_Mime.h | 125 + src/lib/Efreet_Trash.h | 102 + src/lib/Makefile.am | 69 + src/lib/efreet.c | 366 +++ src/lib/efreet_base.c | 387 +++ src/lib/efreet_base.h | 86 + src/lib/efreet_cache.c | 1398 +++++++++++ src/lib/efreet_cache_private.h | 63 + src/lib/efreet_desktop.c | 1085 ++++++++ src/lib/efreet_desktop.h | 370 +++ src/lib/efreet_desktop_command.c | 919 +++++++ src/lib/efreet_icon.c | 910 +++++++ src/lib/efreet_icon.h | 249 ++ src/lib/efreet_ini.c | 629 +++++ src/lib/efreet_ini.h | 181 ++ src/lib/efreet_menu.c | 3887 +++++++++++++++++++++++++++++ src/lib/efreet_menu.h | 138 + src/lib/efreet_mime.c | 1622 ++++++++++++ src/lib/efreet_private.h | 227 ++ src/lib/efreet_trash.c | 288 +++ src/lib/efreet_uri.c | 119 + src/lib/efreet_uri.h | 62 + src/lib/efreet_utils.c | 488 ++++ src/lib/efreet_utils.h | 157 ++ src/lib/efreet_xml.c | 609 +++++ src/lib/efreet_xml.h | 59 + src/tests/Makefile.am | 66 + src/tests/compare/Makefile.am | 15 + src/tests/compare/comp.h | 527 ++++ src/tests/compare/efreet_alloc.c | 30 + src/tests/compare/efreet_menu_alloc.c | 23 + src/tests/data/Makefile.am | 18 + src/tests/data/entry | Bin 0 -> 647 bytes src/tests/data/entry.png | Bin 0 -> 648 bytes src/tests/data/long.ini | 3 + src/tests/data/preferences.menu | 41 + src/tests/data/sub/Makefile.am | 8 + src/tests/data/sub/test.desktop | 5 + src/tests/data/test.desktop | 8 + src/tests/data/test.ini | 21 + src/tests/data/test.menu | 52 + src/tests/data/test_garbage | 2341 +++++++++++++++++ src/tests/data/test_menu_bad_comment.menu | 14 + src/tests/data/test_menu_slash_bad.menu | 11 + src/tests/data/test_type.desktop | 8 + src/tests/ef_cache.c | 199 ++ src/tests/ef_data_dirs.c | 330 +++ src/tests/ef_desktop.c | 401 +++ src/tests/ef_icon_theme.c | 605 +++++ src/tests/ef_ini.c | 174 ++ src/tests/ef_locale.c | 85 + src/tests/ef_menu.c | 150 ++ src/tests/ef_mime.c | 57 + src/tests/ef_test.h | 11 + src/tests/ef_utils.c | 28 + src/tests/efreet_icon_cache_dump.c | 120 + src/tests/efreet_spec_test.c | 57 + src/tests/efreet_suite.c | 103 + src/tests/efreet_suite.h | 10 + src/tests/efreet_test_efreet.c | 25 + src/tests/efreet_test_efreet_cache.c | 25 + src/tests/main.c | 188 ++ 120 files changed, 26669 insertions(+) create mode 100644 .gitignore create mode 100644 AUTHORS create mode 100644 COPYING create mode 100644 ChangeLog create mode 100644 INSTALL create mode 100644 Makefile.am create mode 100644 NEWS create mode 100644 NOTES create mode 100644 README create mode 100644 TODO create mode 100755 autogen.sh create mode 100644 configure.ac create mode 100644 doc/Doxyfile.in create mode 100644 doc/Makefile.am create mode 100644 doc/e.css create mode 100644 doc/foot.html create mode 100644 doc/head.html create mode 100644 doc/images/e.png create mode 100644 doc/images/edoxy.css create mode 100644 doc/images/foot_bg.png create mode 100644 doc/images/head_bg.png create mode 100644 doc/images/menu_bg.png create mode 100644 doc/images/menu_bg_current.png create mode 100644 doc/images/menu_bg_hover.png create mode 100644 doc/images/menu_bg_last.png create mode 100644 doc/images/menu_bg_unsel.png create mode 100644 efreet-mime.pc.in create mode 100644 efreet-trash.pc.in create mode 100644 efreet.pc.in create mode 100644 efreet.spec.in create mode 100644 m4/ac_attribute.m4 create mode 100644 m4/ac_define_dir.m4 create mode 100644 m4/efl_compiler_flag.m4 create mode 100644 m4/efl_coverage.m4 create mode 100644 m4/efl_doxygen.m4 create mode 100644 m4/efl_path_max.m4 create mode 100644 m4/efl_tests.m4 create mode 100644 po/LINGUAS create mode 100644 po/Makevars create mode 100644 po/POTFILES.in create mode 100644 po/cs.po create mode 100644 po/de.po create mode 100644 po/efreet.pot create mode 100644 po/el.po create mode 100644 po/es.po create mode 100644 po/fr.po create mode 100644 po/it.po create mode 100644 po/ja.po create mode 100644 po/ko.po create mode 100644 po/nl.po create mode 100644 po/pt.po create mode 100644 po/ru.po create mode 100644 po/sl.po create mode 100644 src/Makefile.am create mode 100644 src/bin/Makefile.am create mode 100644 src/bin/efreet_desktop_cache_create.c create mode 100644 src/bin/efreet_icon_cache_create.c create mode 100644 src/lib/Efreet.h create mode 100644 src/lib/Efreet_Mime.h create mode 100644 src/lib/Efreet_Trash.h create mode 100644 src/lib/Makefile.am create mode 100644 src/lib/efreet.c create mode 100644 src/lib/efreet_base.c create mode 100644 src/lib/efreet_base.h create mode 100644 src/lib/efreet_cache.c create mode 100644 src/lib/efreet_cache_private.h create mode 100644 src/lib/efreet_desktop.c create mode 100644 src/lib/efreet_desktop.h create mode 100644 src/lib/efreet_desktop_command.c create mode 100644 src/lib/efreet_icon.c create mode 100644 src/lib/efreet_icon.h create mode 100644 src/lib/efreet_ini.c create mode 100644 src/lib/efreet_ini.h create mode 100644 src/lib/efreet_menu.c create mode 100644 src/lib/efreet_menu.h create mode 100644 src/lib/efreet_mime.c create mode 100644 src/lib/efreet_private.h create mode 100644 src/lib/efreet_trash.c create mode 100644 src/lib/efreet_uri.c create mode 100644 src/lib/efreet_uri.h create mode 100644 src/lib/efreet_utils.c create mode 100644 src/lib/efreet_utils.h create mode 100644 src/lib/efreet_xml.c create mode 100644 src/lib/efreet_xml.h create mode 100644 src/tests/Makefile.am create mode 100644 src/tests/compare/Makefile.am create mode 100644 src/tests/compare/comp.h create mode 100644 src/tests/compare/efreet_alloc.c create mode 100644 src/tests/compare/efreet_menu_alloc.c create mode 100644 src/tests/data/Makefile.am create mode 100644 src/tests/data/entry create mode 100644 src/tests/data/entry.png create mode 100644 src/tests/data/long.ini create mode 100644 src/tests/data/preferences.menu create mode 100644 src/tests/data/sub/Makefile.am create mode 100644 src/tests/data/sub/test.desktop create mode 100644 src/tests/data/test.desktop create mode 100644 src/tests/data/test.ini create mode 100644 src/tests/data/test.menu create mode 100644 src/tests/data/test_garbage create mode 100644 src/tests/data/test_menu_bad_comment.menu create mode 100644 src/tests/data/test_menu_slash_bad.menu create mode 100644 src/tests/data/test_type.desktop create mode 100644 src/tests/ef_cache.c create mode 100644 src/tests/ef_data_dirs.c create mode 100644 src/tests/ef_desktop.c create mode 100644 src/tests/ef_icon_theme.c create mode 100644 src/tests/ef_ini.c create mode 100644 src/tests/ef_locale.c create mode 100644 src/tests/ef_menu.c create mode 100644 src/tests/ef_mime.c create mode 100644 src/tests/ef_test.h create mode 100644 src/tests/ef_utils.c create mode 100644 src/tests/efreet_icon_cache_dump.c create mode 100644 src/tests/efreet_spec_test.c create mode 100644 src/tests/efreet_suite.c create mode 100644 src/tests/efreet_suite.h create mode 100644 src/tests/efreet_test_efreet.c create mode 100644 src/tests/efreet_test_efreet_cache.c create mode 100644 src/tests/main.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..99c665b --- /dev/null +++ b/.gitignore @@ -0,0 +1,152 @@ +*.o +*.lo +*.la +.deps +.libs +ABOUT-NLS +Makefile +Makefile.in +/aclocal.m4 +autom4te.cache/ +/config.cache +/config.cache-env +/config.guess +/config.h +/config.h.in +/config.log +/config.status +/config.sub +/config.rpath +/configure +/depcomp +doc/Doxyfile +doc/Makefile +doc/Makefile.in +/doc/efreet_doxy_warnings.txt +/doc/html/ +/doc/latex/ +/efreet-mime.pc +/efreet-trash.pc +/efreet.pc +/efreet.spec +/install-sh +/libtool +/ltmain.sh +m4/libtool.m4 +m4/ltoptions.m4 +m4/ltsugar.m4 +m4/ltversion.m4 +m4/lt~obsolete.m4 +/missing +src/bin/efreet_desktop_cache_create +src/bin/efreet_icon_cache_create +src/tests/compare/efreet_alloc +src/tests/compare/efreet_menu_alloc +src/tests/efreet_cache_test +src/tests/efreet_icon_cache_dump +src/tests/efreet_spec_test +src/tests/efreet_test +/m4/codeset.m4 +/m4/gettext.m4 +/m4/glibc21.m4 +/m4/iconv.m4 +/m4/intdiv0.m4 +/m4/intmax.m4 +/m4/inttypes-pri.m4 +/m4/inttypes.m4 +/m4/inttypes_h.m4 +/m4/isc-posix.m4 +/m4/lcmessage.m4 +/m4/lib-ld.m4 +/m4/lib-link.m4/m4/codeset.m4 +/m4/gettext.m4 +/m4/glibc21.m4 +/m4/iconv.m4 +/m4/intdiv0.m4 +/m4/intmax.m4 +/m4/inttypes-pri.m4 +/m4/inttypes.m4 +/m4/inttypes_h.m4 +/m4/isc-posix.m4 +/m4/lcmessage.m4 +/m4/lib-ld.m4 +/m4/lib-link.m4 +/m4/lib-prefix.m4 +/m4/libtool.m4 +/m4/longdouble.m4 +/m4/longlong.m4 +/m4/ltoptions.m4 +/m4/ltsugar.m4 +/m4/ltversion.m4 +/m4/lt~obsolete.m4 +/m4/nls.m4 +/m4/po.m4 +/m4/printf-posix.m4 +/m4/progtest.m4 +/m4/signed.m4 +/m4/size_max.m4 +/m4/stdint_h.m4 +/m4/uintmax_t.m4 +/m4/ulonglong.m4 +/m4/wchar_t.m4 +/m4/wint_t.m4 +/m4/xsize.m4 +/m4/glibc2.m4 +/m4/lib-prefix.m4 +/m4/libtool.m4 +/m4/longdouble.m4 +/m4/longlong.m4 +/m4/ltoptions.m4 +/m4/ltsugar.m4 +/m4/ltversion.m4 +/m4/lt~obsolete.m4 +/m4/nls.m4 +/m4/po.m4 +/m4/printf-posix.m4 +/m4/progtest.m4 +/m4/signed.m4 +/m4/size_max.m4 +/m4/stdint_h.m4 +/m4/uintmax_t.m4 +/m4/ulonglong.m4 +/m4/wchar_t.m4 +/m4/wint_t.m4 +/m4/xsize.m4 +/m4/glibc2.m4 +/m4/intl.m4 +/m4/intldir.m4 +/m4/intlmacosx.m4 +/m4/lock.m4 +/m4/visibility.m4/m4/intl.m4 +/m4/intldir.m4 +/m4/intlmacosx.m4 +/m4/lock.m4 +/m4/visibility.m4 +/po/Makefile.in.in +/po/Makevars.template +/po/POTFILES +/po/Rules-quot +/po/boldquot.sed +/po/ecore.pot +/po/en@boldquot.header +/po/en@quot.header +/po/insert-header.sin +/po/quot.sed +/po/remove-potcdate.sed +/po/remove-potcdate.sin +/po/stamp-po +/po/Makefile.in.in +/po/Makevars.template +/po/POTFILES +/po/Rules-quot +/po/boldquot.sed +/po/ecore.pot +/po/en@boldquot.header +/po/en@quot.header +/po/insert-header.sin +/po/quot.sed +/po/remove-potcdate.sed +/po/remove-potcdate.sin +/po/*.gmo +/po/stamp-po +/stamp-h1 diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..ad4bc4c --- /dev/null +++ b/AUTHORS @@ -0,0 +1,7 @@ +dan 'dj2' sinclair +Brian 'rephorm' Mattern +Sebastian Dransfeld +Nathan 'RbdPngn' Ingersoll +Nicholas 'Mekius' Hughart +Albin "Lutin" Tonnerre +Mathieu Taillefumier diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..91c13e7 --- /dev/null +++ b/COPYING @@ -0,0 +1,25 @@ +Copyright notice for Efreet: + +Copyright (C) 2006-2011 Dan Sinclair and various contributors (see AUTHORS) + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..46f0995 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,132 @@ +2011-01-29 Carsten Haitzler (The Rasterman) + + 1.0.0 release + +2011-01-29 Brian Mattern + + Fix bug when you have an empty in a menu + +2011-01-30 Carsten Haitzler (The Rasterman) + + Fix tests to not build clearenv related code if not available + +2011-01-31 Sebastian Dransfeld + + * Fix memleak, free cache icons after retrival + * Add temporary memory cache of eet file data + * Always use icon cache, remove old code + * Make check for stat return more explicit + * Check whether dirs is NULL + * Create internal struct for theme cache + * Check if theme has changed when building cache + * Prefix internal efreet cache keys with __ + * Create one cache file for each theme + * Before doing strcmp, check if pointers are equal. They might be + because of mmap and stringshare. + * Add local icon cache, so we wont hit the cache file for each icon + to be retrieved + +2011-02-03 Sebastian Dransfeld + + * Fix leak in efreet_mime_type_icon_get() + +2011-02-05 Sebastian Dransfeld + + * Remove global log domain and make all files define log domain + before including efreet_private.h + * Remove efreet_xml_{init,shutdown}() from efreet_menu.c, it is done + in efreet_init() + * Set EFREET_MODULE_LOG_DOM variable to -1 after unregister + * Remove duplicate logging macros + +2011-02-05 Hannes Janetzek + + * Clear mem cache when closing eet file + +2011-02-06 Sebastian Dransfeld + + * Fix doc for efreet_utils.{h,c},efreet_mime.c + * Add comment for all internal EAPI functions + * Remove doc for removed _efreet_log_domain_global + +2011-02-07 Sebastian Dransfeld + + * Don't create cache dir several times + * Do efreet_init before using efreet_*() functions + * Move lock creation to own function for desktop cache + * Set file owner to calling user + +2011-02-09 Sebastian Dransfeld + + * Move theme hash from efreet_icon.c to efreet_cache.c + * Don't free data returned by efreet_util_cache_names + * Add free callback to eet hashes + * Remove unneeded header + * Check if pattern equals "*" before doing pattern match + * Remove EAPI from efreet_home_dir_get + * Make efreet_cache_icon_theme_free only used in efreet_cache.c static + * Move struct only used for cache to efreet_cache_private.h + +2011-02-10 Sebastian Dransfeld + + * Move all eet cache handling to efreet_cache.c + * Free hashes on init error + * efreet_cache_icon -> efreet_icon for functions in efreet_icon.c + * Fix memleak in desktop cache create + * Pass dirs to desktop cache process as arguments + * Delay cache recreation with a timer + * Move desktop cache to efreet_cache.c, and cache all requests which + hit the eet cache + +2011-03-18 Mike Blumenkrantz + + * Use eina_log more effectively + +2011-04-04 Tom Hacohen (TAsn) + + * Fixed uri encoding when opening files. + +2011-08-16 Sebastian Dransfeld + + * Always rebuild cache from scratch when needed, but rely on correct + spec behaviour to check for theme changes. This will considerably + speed up the cache process when there is no change, and improve the + correctness of the cache when changes occur. For example didn't the + previous behaviour handle file removal gracefully. + +2011-08-16 Sebastian Dransfeld + + * Save whether cache changed in update file, and propagate to update + event. + +2011-12-02 Carsten Haitzler (The Rasterman) + + 1.1.0 release + +2012-01-19 Carsten Haitzler (The Rasterman) + + * use eina_file_direct_ls not opendir() + +2012-01-19 Jérôme Pinot + + * Documentation fixes. + +2012-04-26 Carsten Haitzler (The Rasterman) + + 1.2.0 release + +2012-05-11 Cedric Bail + + * Faster loading of mime type. + +2012-06-25 Sebastian Dransfeld + + * Added efreet_lang_reset() to reset language dependent variables and caches + +2012-06-25 Sebastian Dransfeld + + * Support XDG_DESKTOP_DIR and read user-dirs.dirs + +2012-07-02 Sebastian Dransfeld + + * Fix minor memory leak in cache update handler diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..23e5f25 --- /dev/null +++ b/INSTALL @@ -0,0 +1,236 @@ +Installation Instructions +************************* + +Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005 Free +Software Foundation, Inc. + +This file is free documentation; the Free Software Foundation gives +unlimited permission to copy, distribute and modify it. + +Basic Installation +================== + +These are generic installation instructions. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, and a +file `config.log' containing compiler output (useful mainly for +debugging `configure'). + + It can also use an optional file (typically called `config.cache' +and enabled with `--cache-file=config.cache' or simply `-C') that saves +the results of its tests to speed up reconfiguring. (Caching is +disabled by default to prevent problems with accidental use of stale +cache files.) + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If you are using the cache, and at +some point `config.cache' contains results you don't want to keep, you +may remove or edit it. + + The file `configure.ac' (or `configure.in') is used to create +`configure' by a program called `autoconf'. You only need +`configure.ac' if you want to change it or regenerate `configure' using +a newer version of `autoconf'. + +The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. If you're + using `csh' on an old version of System V, you might need to type + `sh ./configure' instead to prevent `csh' from trying to execute + `configure' itself. + + Running `configure' takes awhile. While running, it prints some + messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Optionally, type `make check' to run any self-tests that come with + the package. + + 4. Type `make install' to install the programs and any data files and + documentation. + + 5. You can remove the program binaries and object files from the + source code directory by typing `make clean'. To also remove the + files that `configure' created (so you can compile the package for + a different kind of computer), type `make distclean'. There is + also a `make maintainer-clean' target, but that is intended mainly + for the package's developers. If you use it, you may have to get + all sorts of other programs in order to regenerate files that came + with the distribution. + +Compilers and Options +===================== + +Some systems require unusual options for compilation or linking that the +`configure' script does not know about. Run `./configure --help' for +details on some of the pertinent environment variables. + + You can give `configure' initial values for configuration parameters +by setting variables in the command line or in the environment. Here +is an example: + + ./configure CC=c89 CFLAGS=-O2 LIBS=-lposix + + *Note Defining Variables::, for more details. + +Compiling For Multiple Architectures +==================================== + +You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you must use a version of `make' that +supports the `VPATH' variable, such as GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. + + If you have to use a `make' that does not support the `VPATH' +variable, you have to compile the package for one architecture at a +time in the source code directory. After you have installed the +package for one architecture, use `make distclean' before reconfiguring +for another architecture. + +Installation Names +================== + +By default, `make install' installs the package's commands under +`/usr/local/bin', include files under `/usr/local/include', etc. You +can specify an installation prefix other than `/usr/local' by giving +`configure' the option `--prefix=PREFIX'. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +pass the option `--exec-prefix=PREFIX' to `configure', the package uses +PREFIX as the prefix for installing programs and libraries. +Documentation and other data files still use the regular prefix. + + In addition, if you use an unusual directory layout you can give +options like `--bindir=DIR' to specify different values for particular +kinds of files. Run `configure --help' for a list of the directories +you can set and what kinds of files go in them. + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + +Optional Features +================= + +Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + +Specifying the System Type +========================== + +There may be some features `configure' cannot figure out automatically, +but needs to determine by the type of machine the package will run on. +Usually, assuming the package is built to be run on the _same_ +architectures, `configure' can figure that out, but if it prints a +message saying it cannot guess the machine type, give it the +`--build=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name which has the form: + + CPU-COMPANY-SYSTEM + +where SYSTEM can have one of these forms: + + OS KERNEL-OS + + See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the machine type. + + If you are _building_ compiler tools for cross-compiling, you should +use the option `--target=TYPE' to select the type of system they will +produce code for. + + If you want to _use_ a cross compiler, that generates code for a +platform different from the build platform, you should specify the +"host" platform (i.e., that on which the generated programs will +eventually be run) with `--host=TYPE'. + +Sharing Defaults +================ + +If you want to set default values for `configure' scripts to share, you +can create a site shell script called `config.site' that gives default +values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Defining Variables +================== + +Variables not defined in a site shell script can be set in the +environment passed to `configure'. However, some packages may run +configure again during the build, and the customized values of these +variables may be lost. In order to avoid this problem, you should set +them in the `configure' command line, using `VAR=value'. For example: + + ./configure CC=/usr/local2/bin/gcc + +causes the specified `gcc' to be used as the C compiler (unless it is +overridden in the site shell script). Here is a another example: + + /bin/bash ./configure CONFIG_SHELL=/bin/bash + +Here the `CONFIG_SHELL=/bin/bash' operand causes subsequent +configuration-related scripts to be executed by `/bin/bash'. + +`configure' Invocation +====================== + +`configure' recognizes the following options to control how it operates. + +`--help' +`-h' + Print a summary of the options to `configure', and exit. + +`--version' +`-V' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`--cache-file=FILE' + Enable the cache: use and save the results of the tests in FILE, + traditionally `config.cache'. FILE defaults to `/dev/null' to + disable caching. + +`--config-cache' +`-C' + Alias for `--cache-file=config.cache'. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. To + suppress all normal output, redirect it to `/dev/null' (any error + messages will still be shown). + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`configure' also accepts some other, not widely useful, options. Run +`configure --help' for more details. + diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..a159015 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,139 @@ +ACLOCAL_AMFLAGS = -I m4 + +SUBDIRS = src doc + +MAINTAINERCLEANFILES = \ +Makefile.in \ +aclocal.m4 \ +config.guess \ +config.h.in \ +config.h.in~ \ +config.rpath \ +config.sub \ +configure \ +depcomp \ +install-sh \ +ltmain.sh \ +missing \ +$(PACKAGE_TARNAME)-$(PACKAGE_VERSION).tar.gz \ +$(PACKAGE_TARNAME)-$(PACKAGE_VERSION).tar.bz2 \ +$(PACKAGE_TARNAME)-$(PACKAGE_VERSION)-doc.tar.bz2 \ +m4/libtool.m4 \ +m4/lt~obsolete.m4 \ +m4/ltoptions.m4 \ +m4/ltsugar.m4 \ +m4/ltversion.m4 \ +m4/codeset.m4 \ +m4/gettext.m4* \ +m4/glibc2*.m4 \ +m4/iconv.m4 \ +m4/intdiv0.m4 \ +m4/intldir.m4 \ +m4/intl.m4 \ +m4/intlmacosx.m4 \ +m4/intmax.m4* \ +m4/inttypes_h.m4 \ +m4/inttypes.m4 \ +m4/inttypes-pri.m4 \ +m4/isc-posix.m4 \ +m4/lcmessage.m4 \ +m4/lib-ld.m4* \ +m4/lib-link.m4 \ +m4/lib-prefix.m4* \ +m4/lock.m4 \ +m4/longdouble.m4* \ +m4/longlong.m4* \ +m4/nls.m4 \ +m4/po.m4* \ +m4/printf-posix.m4* \ +m4/progtest.m4 \ +m4/signed.m4 \ +m4/size_max.m4* \ +m4/stdint_h.m4 \ +m4/uintmax_t.m4 \ +m4/ulonglong.m4* \ +m4/visibility.m4 \ +m4/wchar_t.m4 \ +m4/wint_t.m4* \ +m4/xsize.m4* + +if HAVE_PO +SUBDIRS += po + +MAINTAINERCLEANFILES += \ + po/boldquot.sed \ + po/en@boldquot.header \ + po/en@quot.header \ + po/insert-header.sin \ + po/Makefile.in.in* \ + po/Makevars.template \ + po/quot.sed \ + po/remove-potcdate.sin \ + po/Rules-quot* + +endif + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = efreet.pc efreet-mime.pc efreet-trash.pc + +EXTRA_DIST = \ +AUTHORS \ +COPYING \ +autogen.sh \ +efreet.pc.in \ +efreet-mime.pc.in \ +efreet.spec.in \ +efreet.spec \ +m4/efl_coverage.m4\ +m4/efl_tests.m4\ +README + +.PHONY: doc coverage + +doc: + @echo "entering doc/" + make -C doc doc + +# Unit tests + +if EFL_ENABLE_TESTS + +check-local: + @./src/tests/efreet_suite + +else + +check-local: + @echo "reconfigure with --enable-tests" + +endif + +# Coverage report + +if EFL_ENABLE_COVERAGE +lcov-reset: + @rm -rf $(top_builddir)/coverage + @find $(top_builddir) -name "*.gcda" -delete + @lcov --zerocounters --directory $(top_builddir) + +lcov-report: + @mkdir $(top_builddir)/coverage + lcov --capture --compat-libtool --output-file $(top_builddir)/coverage/coverage.info --directory $(top_builddir) + lcov --remove $(top_builddir)/coverage/coverage.info '*.h' --output-file $(top_builddir)/coverage/coverage.cleaned.info + genhtml -t "$(PACKAGE_STRING)" -o $(top_builddir)/coverage/html $(top_builddir)/coverage/coverage.cleaned.info + @echo "Coverage Report at $(top_builddir)/coverage/html" + +coverage: + @$(MAKE) lcov-reset + @$(MAKE) check + @$(MAKE) lcov-report +else +lcov-reset: + @echo "reconfigure with --enable-coverage" + +lcov-report: + @echo "reconfigure with --enable-coverage" + +coverage: + @echo "reconfigure with --enable-tests --enable-coverage" +endif diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..12d5666 --- /dev/null +++ b/NEWS @@ -0,0 +1,58 @@ +Efreet 1.7.0 + +Changes since Efreet 1.2.0: +--------------------------- + +Improvements: + + * Faster mime type loading. + + +Additions: + + * Support XDG_DESKTOP_DIR + * efreet_lang_reset() for refreshing language variables and caches after locale switches + +Efreet 1.2.0 + +Changes since Efreet 1.1.0: +--------------------------- + +Improvements: + + * use eina_file_direct_ls() instead of opendir(). + * compilation warnings fixed. + * documentation improvements/fixes + +Changes since Efreet 1.0.0: +--------------------------- + +Fixes: + + * when you have an empty in a menu + * tests don't do clearenv when it is not available + * memory leak fixed in cache icon retrieval + * check for NULL dirs in caches + * check for theme change when rebuilding cache + * leak in efreet_mime_type_icon_get + * reset log domain to -1 on unregister + * clear memory cache when closing eet file + * fixed documentation + * remove EAPI from efreet_home_dir_get as it wasn't exported in .h's + * don't leak hashes on init error + * no more memleak in desktop cache creation + * fixed uri encoding when opening files + +Improvements: + + * added temporary memory cache for eet file data + * always use icon cache + * faster string comapre with poter equality for strings + * local log domains per file + * remove exess init functions for xml + * stop creating cache dir multiple times + * change ownership of cache files to calling user + * fast path for "*" pattern match + * delay cache creation with a timer to avoid storms + * always rebuild cache from 0 when needed and be more correct + * store cache change flags in update file and propagate to update event diff --git a/NOTES b/NOTES new file mode 100644 index 0000000..485be66 --- /dev/null +++ b/NOTES @@ -0,0 +1,48 @@ + +rephorm one thing. if you plan to add .desktop editing, according to the +spec you need to keep EVERYTHING around (even sections / keys you don't know +/ care about) +dj2 k +rephorm but for most usage (read only) thats a waste of memory +dj2 hm, i guess the best way to do that is keep everything in a hash +dj2 and write accessor functions +dj2 or pull the common stuff out to functions and leve the rest in the hash +rephorm and maybe have a flag for 'readonly' to kill the hash +dj2 yea, will have to put some tought into the api for that + +rephorm ooh. comments need to be preserved also in edits + + + + +rephorm efreet_xml has one bug that i see +rephorm if you have something like beforeafter, you +can't get to the 'after' text +dj2 hm, will have to look at that +rephorm (it stores 'before' as the text on 'tag') +rephorm it should probably create child nodes with the text +rephorm so tag would have 3 children, text: before, tag: child and text: after + + +efreet_desktop_string_list_parse() needs to optionally use comma (',') as +the separator if the desktop version is < 1.0 (but, what if it isn't set??) + + +desktop_command_get: + check for needed types (nNfFuU) + + get lists of needed types (dirs, fullpaths, urls, etc) + + if type in uU: + create Pending struct + start downloads, pass Pending in as data + else: + if tyep in UF... + exec + +download cb: + if type in fF + + + + diff --git a/README b/README new file mode 100644 index 0000000..05ea494 --- /dev/null +++ b/README @@ -0,0 +1,38 @@ +Efreet 1.7.0 + +****************************************************************************** + + FOR ANY ISSUES PLEASE EMAIL: + enlightenment-devel@lists.sourceforge.net + +****************************************************************************** + +Requirements: +------------- + +Must: + libc + eina (at least 1.1.0) + ecore (at least 1.1.0) + ecore-file (at least 1.1.0) + eet (at least 1.5.0) + +An implementation of several specifications from freedesktop.org intended for +use in Enlightenment DR17 (e17) and other applications using the Enlightenment +Foundation Libraries (EFL). Currently, the following specifications are +included: + * Base Directory + * Desktop Entry + * Icon Theme + * Menu + * Trash + * Mime + +------------------------------------------------------------------------------ +COMPILING AND INSTALLING: + + ./configure + make +(as root unless you are installing in your users directories): + make install + diff --git a/TODO b/TODO new file mode 100644 index 0000000..a40fb31 --- /dev/null +++ b/TODO @@ -0,0 +1,24 @@ +TODO +---- +- efreet_desktop_command_* should free the exec strings for you (set free cb on execs list in _build()). need to make sure users of this function are updated when this change is made (e.g. e17's exec cb) +- Efreet_Menu should setup an Ecore_File_Monitor on the .menu files and the + app_dir and reload the menu when needed + +- The move handling is wrong when moving to a destination with /'s in the + name. We should be creating intermediate menus for each of the /'d items + instead of just making the name as we do now + +- Add support for searching for a list of fallback icons in the current theme + BEFORE looking in the inherited themes (e.g. for mime-types that go from + specific to general). + +- If a theme has several icon types (png, xpm..) prefer the first in the + extension list. + +- Add some ref/free tracking to check if users aren't abusing the system. + +Notes from the Menu Spec test +----------------------------- +- We're appending the / to the menu names in the test case. We should + possibly be doing that in the efreet code itself. + diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..2a59cac --- /dev/null +++ b/autogen.sh @@ -0,0 +1,39 @@ +#!/bin/sh + +rm -rf autom4te.cache +rm -f aclocal.m4 ltmain.sh + +touch ABOUT-NLS + +echo "Running autopoint..." ; autopoint -f || : +echo "Running aclocal..." ; aclocal $ACLOCAL_FLAGS -I m4 || exit 1 +echo "Running autoheader..." ; autoheader || exit 1 +echo "Running autoconf..." ; autoconf || exit 1 +echo "Running libtoolize..." ; (libtoolize --copy --automake || glibtoolize --automake) || exit 1 +echo "Running automake..." ; automake --add-missing --copy --gnu || exit 1 + +W=0 + +rm -f config.cache-env.tmp +echo "OLD_PARM=\"$@\"" >> config.cache-env.tmp +echo "OLD_CFLAGS=\"$CFLAGS\"" >> config.cache-env.tmp +echo "OLD_PATH=\"$PATH\"" >> config.cache-env.tmp +echo "OLD_PKG_CONFIG_PATH=\"$PKG_CONFIG_PATH\"" >> config.cache-env.tmp +echo "OLD_LDFLAGS=\"$LDFLAGS\"" >> config.cache-env.tmp + +cmp config.cache-env.tmp config.cache-env >> /dev/null +if [ $? -ne 0 ]; then + W=1; +fi + +if [ $W -ne 0 ]; then + echo "Cleaning configure cache..."; + rm -f config.cache config.cache-env + mv config.cache-env.tmp config.cache-env +else + rm -f config.cache-env.tmp +fi + +if [ -z "$NOCONFIGURE" ]; then + ./configure -C "$@" +fi diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..9535101 --- /dev/null +++ b/configure.ac @@ -0,0 +1,285 @@ +##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--## +##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--## +m4_define([v_maj], [1]) +m4_define([v_min], [7]) +m4_define([v_mic], [0]) +m4_define([v_rev], m4_esyscmd([(svnversion "${SVN_REPO_PATH:-.}" | grep -v '\(export\|Unversioned directory\)' || echo 0) | awk -F : '{printf("%s\n", $1);}' | tr -d ' :MSP\n'])) +m4_if(v_rev, [0], [m4_define([v_rev], m4_esyscmd([git log 2> /dev/null | (grep -m1 git-svn-id || echo 0) | sed -e 's/.*@\([0-9]*\).*/\1/' | tr -d '\n']))]) +##-- When released, remove the dnl on the below line +m4_undefine([v_rev]) +##-- When doing snapshots - change soname. remove dnl on below line +dnl m4_define([relname], [ver-pre-svn-07]) +dnl m4_define([v_rel], [-release relname]) +##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--## +m4_ifdef([v_rev], [m4_define([v_ver], [v_maj.v_min.v_mic.v_rev])], [m4_define([v_ver], [v_maj.v_min.v_mic])]) +m4_define([lt_cur], m4_eval(v_maj + v_min)) +m4_define([lt_rev], v_mic) +m4_define([lt_age], v_min) +##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--## +##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--## + +AC_INIT([efreet], [v_ver], [enlightenment-devel@lists.sourceforge.net]) +AC_PREREQ([2.52]) +AC_CONFIG_SRCDIR([configure.ac]) +AC_CONFIG_MACRO_DIR([m4]) + +AC_CONFIG_HEADERS([config.h]) +AH_TOP([ +#ifndef EFL_CONFIG_H__ +#define EFL_CONFIG_H__ +]) +AH_BOTTOM([ +#endif /* EFL_CONFIG_H__ */ +]) + +AM_INIT_AUTOMAKE([1.6 dist-bzip2]) +m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) + +AC_LIBTOOL_WIN32_DLL +define([AC_LIBTOOL_LANG_CXX_CONFIG], [:])dnl +define([AC_LIBTOOL_LANG_F77_CONFIG], [:])dnl +AC_PROG_LIBTOOL + +##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--## +##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--## +m4_ifdef([v_rev], , [m4_define([v_rev], [0])]) +m4_ifdef([v_rel], , [m4_define([v_rel], [])]) +AC_DEFINE_UNQUOTED(VMAJ, [v_maj], [Major version]) +AC_DEFINE_UNQUOTED(VMIN, [v_min], [Minor version]) +AC_DEFINE_UNQUOTED(VMIC, [v_mic], [Micro version]) +AC_DEFINE_UNQUOTED(VREV, [v_rev], [Revison]) +version_info="lt_cur:lt_rev:lt_age" +release_info="v_rel" +AC_SUBST(version_info) +AC_SUBST(release_info) +##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--## +##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--##--## +VMAJ=v_maj +AC_SUBST(VMAJ) + +AM_GNU_GETTEXT_VERSION(0.17) + +m4_ifdef([AM_GNU_GETTEXT], [ +AM_GNU_GETTEXT([external]) +po_makefile_in=po/Makefile.in +have_po="yes" +],[ +have_po="no" +]) +AC_SUBST(LTLIBINTL) +LOCALE_DIR="${localedir}" +AC_SUBST(LOCALE_DIR) + +if test "x${POSUB}" = "x" ; then + have_po="no" +fi + +AM_CONDITIONAL([HAVE_PO], [test "x${have_po}" = "xyes"]) + +### Needed information + +AC_CANONICAL_BUILD +AC_CANONICAL_HOST + + +### Additional options to configure + +AC_ARG_ENABLE([strict-spec], + [AC_HELP_STRING([--enable-strict-spec], [Enable strict spec compliance @<:@default=disabled@:>@])], + [ + if test "x${enableval}" = "xyes" ; then + enable_strict_spec="yes" + else + enable_strict_spec="no" + fi + ], + [enable_strict_spec="no"]) + +if test "x${enable_strict_spec}" = "xyes" ; then + AC_DEFINE([STRICT_SPEC], [1], [Strict Spec Compliance]) +fi + +AC_ARG_ENABLE([sloppy-spec], + [AC_HELP_STRING([--disable-sloppy-spec], [Enable sloppy spec compliance @<:@default=enabled@:>@])], + [ + if test "x${enableval}" = "xyes" ; then + enable_sloppy_spec="yes" + else + enable_sloppy_spec="no" + fi + ], + [enable_sloppy_spec="yes"]) + +if test "x${enable_sloppy_spec}" = "xyes" ; then + AC_DEFINE([SLOPPY_SPEC], [1], [Sloppy Spec Compliance]) +fi + + +#AC_ARG_ENABLE(hidden-visibility, +# [AC_HELP_STRING([--enable-hidden-visibility],[Enable hidden visibility])], +# [enable_hidden_visibility=$enableval], [enable_hidden_visibility="auto"]) +#if test "x$enable_hidden_visibility" = "xyes" ; then +# CPPFLAGS="$CPPFLAGS -fvisibility=hidden" +#else +# AC_DEFINE(DEFAULT_VISIBLITY, 1, [Default visibility]) +#fi +#AM_CONDITIONAL(DEFAULT_VISIBILITY, test "x$enable_hidden_visibility" != "xyes") +AM_CONDITIONAL(DEFAULT_VISIBILITY, false) + + +### Checks for programs +AC_PROG_CC + +# pkg-config +PKG_PROG_PKG_CONFIG + +# Check whether pkg-config supports Requires.private +if $PKG_CONFIG --atleast-pkgconfig-version 0.22; then + pkgconfig_requires_private="Requires.private" +else + pkgconfig_requires_private="Requires" +fi +AC_SUBST(pkgconfig_requires_private) + +# doxygen program for documentation building +EFL_CHECK_DOXYGEN([build_doc="yes"], [build_doc="no"]) + + +### Checks for libraries + +# Evil library for compilation on Windows CE + +EFL_EFREET_BUILD="" +EFL_EFREET_MIME_BUILD="" +EFL_EFREET_TRASH_BUILD="" +case "$host_os" in + mingw*) + PKG_CHECK_MODULES([EVIL], [evil >= 1.6.99]) + AC_DEFINE(HAVE_EVIL, 1, [Set to 1 if evil package is installed]) + requirement_efreet="evil" + EFL_EFREET_BUILD="-DEFL_EFREET_BUILD" + EFL_EFREET_MIME_BUILD="-DEFL_EFREET_MIME_BUILD" + EFL_EFREET_TRASH_BUILD="-DEFL_EFREET_TRASH_BUILD" + ;; +esac +AC_SUBST(EFL_EFREET_BUILD) +AC_SUBST(EFL_EFREET_MIME_BUILD) +AC_SUBST(EFL_EFREET_TRASH_BUILD) + +requirement_efreet="ecore-file >= 1.6.99 ecore >= 1.6.99 eet >= 1.6.99 eina >= 1.6.99 ${requirement_efreet}" +PKG_CHECK_MODULES(EFREET, [${requirement_efreet}]) +PKG_CHECK_MODULES(EINA, [eina >= 1.6.99]) + + +### Checks for header files + +AC_CHECK_HEADERS([netinet/in.h arpa/inet.h]) + +### Checks for types + + +### Checks for structures + + +### Checks for compiler characteristics + +AC_C_CONST +AC_C___ATTRIBUTE__ +AM_PROG_CC_STDC +AC_HEADER_STDC + +case "${host_os}" in + mingw*) + EFREET_CFLAGS="${EFREET_CFLAGS} ${EVIL_CFLAGS}" + ;; +esac + +if ! test "x${VMIC}" = "x" ; then + EFL_COMPILER_FLAG([-Wall]) + EFL_COMPILER_FLAG([-W]) +fi + +EFL_COMPILER_FLAG([-Wshadow]) + +EFL_CHECK_PATH_MAX + + +### Checks for linker characteristics +lt_enable_auto_import="" +WIN32_LIBS="" +case "${host_os}" in + mingw*) + WIN32_LIBS="-lws2_32" + lt_enable_auto_import="-Wl,--enable-auto-import" + ;; +esac +AC_SUBST(WIN32_LIBS) +AC_SUBST(lt_enable_auto_import) + + +### Checks for library functions +AC_ISC_POSIX +AC_FUNC_ALLOCA +AC_CHECK_FUNCS(strlcpy clearenv) + +### Unit tests, coverage + +EFL_CHECK_TESTS([enable_tests="yes"], [enable_tests="no"]) + +EFL_CHECK_COVERAGE([${enable_tests}], [enable_coverage="yes"], [enable_coverage="no"]) +EFREET_CFLAGS="${EFREET_CFLAGS} ${EFL_COVERAGE_CFLAGS}" +EFREET_LIBS="${EFREET_LIBS} ${EFL_COVERAGE_LIBS}" +if test "x$enable_coverage" = "xyes" ; then + EFREET_CFLAGS="${EFREET_CFLAGS} ${EFL_DEBUG_CFLAGS}" +fi + +AC_SUBST(requirement_efreet) + +AC_OUTPUT([ +efreet.spec +efreet.pc +efreet-mime.pc +efreet-trash.pc +Makefile +doc/Makefile +doc/Doxyfile +src/Makefile +src/lib/Makefile +src/bin/Makefile +src/tests/Makefile +src/tests/data/Makefile +src/tests/data/sub/Makefile +src/tests/compare/Makefile +$po_makefile_in +]) + + +##################################################################### +## Info + +echo +echo +echo +echo "------------------------------------------------------------------------" +echo "$PACKAGE $VERSION" +echo "------------------------------------------------------------------------" +echo +echo "Configuration Options Summary:" +echo +echo " Specification compliance:" +echo " Strict.............: ${enable_strict_spec}" +echo " Sloppy.............: ${enable_sloppy_spec}" +echo +echo " Tests................: ${enable_tests}" +echo " Coverage.............: ${enable_coverage}" +echo +echo " Documentation........: ${build_doc}" +echo +echo "Compilation............: make (or gmake)" +echo " CPPFLAGS.............: $CPPFLAGS" +echo " CFLAGS...............: $CFLAGS" +echo " LDFLAGS..............: $LDFLAGS" +echo +echo "Installation...........: make install (as root if needed, with 'su' or 'sudo')" +echo " prefix...............: $prefix" +echo diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in new file mode 100644 index 0000000..cd56fea --- /dev/null +++ b/doc/Doxyfile.in @@ -0,0 +1,1692 @@ +# Doxyfile 1.7.3 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" "). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = Efreet + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = @PACKAGE_VERSION@ + +# Using the PROJECT_BRIEF tag one can provide an optional one line description for a project that appears at the top of each page and should give viewer a quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify an logo or icon that is +# included in the documentation. The maximum height of the logo should not +# exceed 55 pixels and the maximum width should not exceed 200 pixels. +# Doxygen will copy the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = . + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, +# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English +# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, +# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, +# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = NO + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful if your file system +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = YES + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given extension. +# Doxygen has a built-in mapping, but you can override or extend it using this +# tag. The format is ext=language, where ext is a file extension, and language +# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C, +# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make +# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C +# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions +# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also makes the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penalty. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will roughly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols + +SYMBOL_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = NO + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespaces are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen +# will list include files with double quotes in the documentation +# rather than with sharp brackets. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = NO + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen +# will sort the (brief and detailed) documentation of class members so that +# constructors and destructors are listed first. If set to NO (the default) +# the constructors will appear in the respective orders defined by +# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. +# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO +# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper type resolution of all parameters of a function it will reject a +# match between the prototype and the implementation of a member function even if there is only one candidate or it is obvious which candidate to choose by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen +# will still accept a match between prototype and implementation in such cases. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or macro consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and macros in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = NO + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = NO + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. +# This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. The create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. +# You can optionally specify a file name after the option, if omitted +# DoxygenLayout.xml will be used as the name of the layout file. + +LAYOUT_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# The WARN_NO_PARAMDOC option can be enabled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = YES + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = @top_srcdir@/src/lib + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh +# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py +# *.f90 *.f *.for *.vhd *.vhdl + +FILE_PATTERNS = *.c \ + *.h + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = @top_srcdir@/doc/images + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. +# If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. +# Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. +# The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty or if +# non of the patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) +# and it is also possible to disable source filtering for a specific pattern +# using *.ext= (so without naming a filter). This option only has effect when +# FILTER_SOURCE_FILES is enabled. + +FILTER_SOURCE_PATTERNS = + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = YES + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. +# Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = YES + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 2 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = efreet_ \ + Efreet_ \ + _efreet_ \ + _Efreet_ \ + EFREET_ \ + _EFREET_ + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = @srcdir@/head.html + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = @srcdir@/foot.html + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = @srcdir@/e.css + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. +# Doxygen will adjust the colors in the stylesheet and background images +# according to this color. Hue is specified as an angle on a colorwheel, +# see http://en.wikipedia.org/wiki/Hue for more information. +# For instance the value 0 represents red, 60 is yellow, 120 is green, +# 180 is cyan, 240 is blue, 300 purple, and 360 is red again. +# The allowed range is 0 to 359. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of +# the colors in the HTML output. For a value of 0 the output will use +# grayscales only. A value of 255 will produce the most vivid colors. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to +# the luminance component of the colors in the HTML output. Values below +# 100 gradually make the output lighter, whereas values above 100 make +# the output darker. The value divided by 100 is the actual gamma applied, +# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, +# and 100 does not change the gamma. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting +# this to NO can help when comparing the output of multiple runs. + +HTML_TIMESTAMP = YES + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. For this to work a browser that supports +# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox +# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). + +HTML_DYNAMIC_SECTIONS = YES + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. + +GENERATE_DOCSET = YES + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.enlightenment.Efreet + +# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. + +DOCSET_PUBLISHER_ID = org.enlightenment.Efreet + +# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. + +DOCSET_PUBLISHER_NAME = Enlightenment + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = YES + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated +# that can be used as input for Qt's qhelpgenerator to generate a +# Qt Compressed Help (.qch) of the generated HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = org.enlightenment.Efreet + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to +# add. For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see +# +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's +# filter section matches. +# +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files +# will be generated, which together with the HTML files, form an Eclipse help +# plugin. To install this plugin and make it available under the help contents +# menu in Eclipse, the contents of the directory containing the HTML and XML +# files needs to be copied into the plugins directory of eclipse. The name of +# the directory within the plugins directory should be the same as +# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before +# the help appears. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have +# this name. + +ECLIPSE_DOC_ID = org.enlightenment.Efreet + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = YES + +# This tag can be used to set the number of enum values (range [0,1..20]) +# that doxygen will group on one line in the generated HTML documentation. +# Note that a value of 0 will completely suppress the enum values from appearing in the overview section. + +ENUM_VALUES_PER_LINE = 1 + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to YES, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). +# Windows users are probably better off using the HTML help feature. + +GENERATE_TREEVIEW = NO + +# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories, +# and Class Hierarchy pages using a tree view instead of an ordered list. + +USE_INLINE_TREES = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open +# links to external symbols imported via tag files in a separate window. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are +# not supported properly for IE 6.0, but are supported on all modern browsers. +# Note that when changing this option you need to delete any form_*.png files +# in the HTML output before the changes have effect. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax +# (see http://www.mathjax.org) which uses client side Javascript for the +# rendering instead of using prerendered bitmaps. Use this if you do not +# have LaTeX installed or if you want to formulas look prettier in the HTML +# output. When enabled you also need to install MathJax separately and +# configure the path to it using the MATHJAX_RELPATH option. + +USE_MATHJAX = NO + +# When MathJax is enabled you need to specify the location relative to the +# HTML output directory using the MATHJAX_RELPATH option. The destination +# directory should contain the MathJax.js script. For instance, if the mathjax +# directory is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the mathjax.org site, so you can quickly see the result without installing +# MathJax, but it is strongly recommended to install a local copy of MathJax +# before deployment. + +MATHJAX_RELPATH = http://www.mathjax.org/mathjax + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box +# for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using +# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets +# (GENERATE_DOCSET) there is already a search function so this one should +# typically be disabled. For large projects the javascript based search engine +# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. + +SEARCHENGINE = NO + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a PHP enabled web server instead of at the web client +# using Javascript. Doxygen will generate the search PHP script and index +# file to put on the web server. The advantage of the server +# based approach is that it scales better to large projects and allows +# full text search. The disadvantages are that it is more difficult to setup +# and does not have live searching capabilities. + +SERVER_BASED_SEARCH = NO + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = YES + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. +# Note that when enabling USE_PDFLATEX this option is only used for +# generating bitmaps for formulas in the HTML output, but not in the +# Makefile that is written to the output directory. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +# If LATEX_SOURCE_CODE is set to YES then doxygen will include +# source code with syntax highlighting in the LaTeX output. +# Note that which sources are shown also depends on other settings +# such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = YES + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. +# This is useful +# if you want to understand what is going on. +# On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = YES + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = YES + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = NO + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = EINA_MAGIC_DEBUG \ + __UNUSED__= \ + EINA_ARG_NONNULL()= \ + EINA_MALLOC= \ + EINA_WARN_UNUSED_RESULT= \ + EAPI= \ + EINA_PURE= \ + EINA_CONST= + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition that overrules the definition found in the source code. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all references to function-like macros +# that are alone on a line, have an all uppercase name, and do not end with a +# semicolon, because these will confuse the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option also works with HAVE_DOT disabled, but it is recommended to +# install and use dot, since it yields more powerful graphs. + +CLASS_DIAGRAMS = NO + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = NO + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is +# allowed to run in parallel. When set to 0 (the default) doxygen will +# base this on the number of processors available in the system. You can set it +# explicitly to a value larger than 0 to get control over the balance +# between CPU load and processing speed. + +DOT_NUM_THREADS = 0 + +# By default doxygen will write a font called Helvetica to the output +# directory and reference it in all dot files that doxygen generates. +# When you want a differently looking font you can specify the font name +# using DOT_FONTNAME. You need to make sure dot is able to find the font, +# which can be done by putting it in a standard location or by setting the +# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory +# containing the font. + +DOT_FONTNAME = Helvetica + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the output directory to look for the +# FreeSans.ttf font (which doxygen will put there itself). If you specify a +# different font using DOT_FONTNAME you can set the path where dot +# can find it using this tag. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = NO + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = NO + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = NO + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will generate a graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = NO + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, svg, gif or svg. +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the +# \mscfile command). + +MSCFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = YES + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES diff --git a/doc/Makefile.am b/doc/Makefile.am new file mode 100644 index 0000000..ad5250b --- /dev/null +++ b/doc/Makefile.am @@ -0,0 +1,32 @@ +MAINTAINERCLEANFILES = Makefile.in Doxyfile efreet_doxy_warnings.txt + +.PHONY: doc + +PACKAGE_DOCNAME = $(PACKAGE_TARNAME)-$(PACKAGE_VERSION)-doc + +if EFL_BUILD_DOC + +doc-clean: + rm -rf html/ latex/ man/ xml/ $(top_builddir)/$(PACKAGE_DOCNAME).tar* + +doc: all doc-clean + $(efl_doxygen) + cp $(srcdir)/images/* html/ + rm -rf $(PACKAGE_DOCNAME).tar* + mkdir -p $(PACKAGE_DOCNAME)/doc + cp -R html/ latex/ $(PACKAGE_DOCNAME)/doc + tar cf $(PACKAGE_DOCNAME).tar $(PACKAGE_DOCNAME)/ + bzip2 -9 $(PACKAGE_DOCNAME).tar + rm -rf $(PACKAGE_DOCNAME)/ + mv $(PACKAGE_DOCNAME).tar.bz2 $(top_builddir) + +clean-local: doc-clean + +else + +doc: + @echo "Documentation not built. Run ./configure --help" + +endif + +EXTRA_DIST = Doxyfile.in $(wildcard images/*.*) e.css head.html foot.html diff --git a/doc/e.css b/doc/e.css new file mode 100644 index 0000000..8697a3a --- /dev/null +++ b/doc/e.css @@ -0,0 +1,218 @@ +/* + Author: + Andres Blanc + DaveMDS Andreoli + + Supported Browsers: + ie7, opera9, konqueror4 and firefox3 + + Please use a different file for ie6, ie5, etc. hacks. +*/ + + +/* Necessary to place the footer at the bottom of the page */ +html, body { + height: 100%; + margin: 0px; + padding: 0px; +} + +#container { + min-height: 100%; + height: auto !important; + height: 100%; + margin: 0 auto -53px; +} + +#footer, #push { + height: 53px; +} + + +* html #container { + height: 100%; +} + +/* Prevent floating elements overflowing containers */ +.clear { + clear: both; + width: 0px; + height: 0px; +} + +/* Flexible & centered layout from 750 to 960 pixels */ +.layout { + max-width: 960px; + min-width: 760px; + margin-left: auto; + margin-right: auto; +} + +body { + /*font-family: Lucida Grande, Helvetica, sans-serif;*/ + font-family: "Bitstream Vera","Vera","Trebuchet MS",Trebuchet,Tahoma,sans-serif +} + +/* Prevent design overflowing the viewport in small resolutions */ +#container { + padding-right: 17px; + padding-left: 17px; + background-image: url(head_bg.png); + background-repeat: repeat-x; +} + +#header { + width: 100%; + height: 102px; +} + +#header h1 { + width: 63px; + height: 63px; + background-image: url(e.png); + background-repeat: no-repeat; + position: absolute; + margin: 0px; +} + +#header h1 span { + display: none; +} + +#header h2 { + display: none; +} + +/* .menu-container is used to set properties common to .menu and .submenu */ +#header .menu-container { +} + +#header .menu-container ul { + list-style-type: none; + list-style-position: inside; + margin: 0; +} + +#header .menu-container li { + display: block; + float: right; +} + +#header .menu { + height: 63px; + display: block; + background-image: url(menu_bg.png); + background-repeat: repeat-x; +} + +#header .menu ul { + height: 100%; + display: block; + background-image: url(menu_bg_last.png); + background-repeat: no-repeat; + background-position: top right; + padding-right: 17px; +} + +#header .menu li { + height: 100%; + text-align: center; + background-image: url(menu_bg_unsel.png); + background-repeat: no-repeat; +} + +#header .menu a { + height: 100%; + display: block; + color: #cdcdcd; + text-decoration: none; + font-size: 10pt; + line-height: 59px; + text-align: center; + padding: 0px 15px 0px 15px; +} + +#header .menu li:hover { + background-image: url(menu_bg_hover.png); + background-repeat: no-repeat; +} + +#header .menu li:hover a { + color: #FFFFFF; +} + +#header .menu li.current { + background-image: url(menu_bg_current.png); + background-repeat: no-repeat; +} + +#header .menu li.current a { + color: #646464; +} + + +/* Hide all the submenus but the current */ +#header .submenu ul { + display: none; +} + +#header .submenu .current { + display: block; +} + +#header .submenu { + font: bold 10px verdana,'Bitstream Vera Sans',helvetica,arial,sans-serif; + margin-top: 10px; +} + +#header .submenu a { + color: #888888; + text-decoration: none; + font-size: 0.9em; + line-height: 15px; + padding:0px 5px 0px 5px; +} + +#header .submenu a:hover { + color: #444444; +} + +#header .submenu li { + border-left: 1px solid #DDDDDD; +} + +#header .submenu li:last-child { + border-left: 0; +} + +#header .doxytitle { + position: absolute; + font-size: 1.8em; + font-weight: bold; + color: #444444; + line-height: 35px; +} + +#header small { + font-size: 0.4em; +} + +#footer { + background-image: url(foot_bg.png); + width: 100%; +} + +#footer table { + width: 100%; + text-align: center; + white-space: nowrap; + padding: 5px 30px 5px 30px; + font-size: 0.8em; + font-family: "Bitstream Vera","Vera","Trebuchet MS",Trebuchet,Tahoma,sans-serif; + color: #888888; +} + +#footer td.copyright { + width: 100%; +} + diff --git a/doc/foot.html b/doc/foot.html new file mode 100644 index 0000000..78ef911 --- /dev/null +++ b/doc/foot.html @@ -0,0 +1,19 @@ + +
+ + + + + + + + + + + diff --git a/doc/head.html b/doc/head.html new file mode 100644 index 0000000..f9be916 --- /dev/null +++ b/doc/head.html @@ -0,0 +1,68 @@ + + + $title + + + + + + + + + + + + + + +
+ + + +
+
diff --git a/doc/images/e.png b/doc/images/e.png new file mode 100644 index 0000000000000000000000000000000000000000..b3884a5cbca7bce5747eb3b8a92145d60511a718 GIT binary patch literal 3825 zcmW-k2|UyPAIGQMS98Ry6)m}iA@`NJZE_o#Ovc73e^-^QHU$G0h_3|cuDljd5QbT#Je?6pNPP^G($^aTorE>q=yral%n2Rktlk|u< zB6SbBG`&ZF=tG%tj9|RLw-;=+A0K(VYgY{_m@_Mu;#9$;A9&jo($nUG3m{pPG9sKJ z)-SrEJ!oZpgRkTbv#N2w?Avv1g`*hj3@X!Cn+Jh2Ml;^V7@mz6b|Fo-YFT&Oc=qw1 z@}>V_jJMQ>kyo}tc5CX?e65}5iLW`a*?=JPI$Jx)*JdpOpdFk$7LNZlSowU@cr#Mjx)>#Vh2qRYMG@4%U~V=*(;bxq6%o;REM)$>G`C7nd~m&p*#P>~ zXHur`Yquq~Y&12(1U{S4izUj!VqeX6#jVc9i4wfL*s)~-o}QjXMEK_BCOa2bR1e|w zfOiwc9TpT4;xRu`3f2tu^&xUXTv-ZkyvdBZz~6YF9A*}l+0tzM1w6ihdTKaGCcM-b zM}+kR##h<)t_vDPGSvAu;zhryW3SGaF8dM)M|gO64tMi+Iy@ydmbFeUae-c2{QNoG z(9m#bJ|Lb-MPIsvICAtT6Qr%J&FlJgLwoz<%m(`4rx$N)fyA(+zm0CXPABZ`Y;G;L z{_bdyy$#F#KH(XXsO3Jb1Q#zc1fq{*U zjYV~JP>U?NSCL2Ad=K{iTFJ6FGWAV|A0&YtJIM(N3JQvei!+~p$aC|>Dl6&yL|#Xg zIX+2Weg&eas2K77JvQjh9i*O~D5!_YojcEE+(AXktE#$w{w%LP_%H5M=b04kBWh}D zxc2O9VLyNW)gECtF*P+|5T~J=o2apg$$#_(d-_h^TCnHtq71c!2zRWxWDpjn!_UuO zcWv<0kxLtcNl8iN)zxTQTLC2}o9m(byTL;$JWs}{A3oS^ZEvS;Z{E>Cp-_rSN|AlCExY6x%V{lP3aGQ&Z}<7hzfQ zF07zj10`OsC@iK}tTCCaB&2|+E-(9l0(Hy>@X!C-*+HtR-ujI2c$AdYyXh^~)F4Il zzzMlLC8a)YPnWbhjIFPa%gD$GmE?PCduwZ^?eCPKudkxBi;LOH8N8LwW`;LSS$4%) zx?ENsdRA0Kgd_bexl4mJQOGB;;>g}!^1**?gy$7$wPR|Ei zVs8!%jpMNr88)kWN^?ogde$9cWo2b)X&FJ2;y!k)F9OgLp&JTI(cO{q^YfcwF#H{j z%mHD&8k`>k(lj9Q-C;Og+VS&=mg%exwn**okO~FI#6R7i^cR~amJZy`{L_nl_Uzd# zP+riFoNM<}Q<iF240XUkipnh)?rRKXHCsH%<~~eP9Nw zkqau%sz*Z!{@*$FcNP?o@?cZr%EaUjMedC|I-tV!fV+9RVQ_4jZKfCbIfH;Kg45E_jJnnwJDq^pApCdwVgPKmHB z!=6@FPNzCr)C6drgvA1wG8MJ9x;!~AS&DhwVSc;3B#qv%<6L7Hx)0{h+mg+0 z0jqK$SZNPm{q3QtspPPKfBq{eiS~+YJS!pb6?`OSoRqamr_%>N_q^qN00y+O`IQf2 z+ONq@jb3~&ch@XXS3^S)t+$ts^fgp^l*D2hS>*bVDk_42(C2Bz#jeZyq=~0Iw_8tY z=KJ_03RcSYhZI2ZA^5$2n^Z zhKHkI8AsB4C}Cb-U2XZaezlWJK@9Yhu?X!asz+tBKS$Zr%IdX3`!1>nqM47)$jG={ z6Hw*j?B(e@8paOM)YQD_>MGBrf)C%jMf%)G{A_Ab!GaWc-rAa)nHj@)ozmsgEFfWL zXIE5Pd(7NL^`S8bMGS3ZguyK+PiESq78xme#+H`U#qGE0lGfJNvkkwHO>4CJD>xwo z1A{jM11ylm>2^1Vo*e(Pl1LDQ5Ws)HygDLK6i)xZ|&>dfsOPKeXm+M(}+hAe4X z6^(1_>#6teN95%pc2{4Es^8g=`ug?jBeWA}$-@!V(E(v)V{=!8L18fV@bGXsTtjrf znx^KxYlUG#U=E@&U;>bLx5xnj0W$+?(|PNsIb8382bo;|XlLL`xUQxqTtr0V=kjuF zbF&(t;zeg?&f;NqvRMLSa&mHZZZ58_PO-bU7l}ggM@B{}!jl?)gFFKHeR&Zq0D+2% zretIY!nj${CML!8^?U#T%DQSrV~56cXy{^gjJZ5-NJt2yBl4*FtzUx9&dx=n^A{Z* z<3>hoGP1Hd>2zVBB+57`HanY674K}2^S*xF8jnx@@#E^^RNIN1oSaWB#cMgJ*Otw*rB&I6XkW|M$G6uvyt`6=4Q|K`kZ_71o=n5$YD92 zJ{1C@Q+zl8Tt!rSyB;@d6scf;|L*S2Lai5x)ZLRPtfZ|?1=~Oy8x#I6Pp+)3br$G^ z42_NbX`h$s5(7DsmzRH{J7@@G0_>a>lRU=kBQ^b#^fF9~Kp@mz9e@J|CF~Z; z=i}oO1g5^dRdnUai@D1~BN_7E-_c)hH&#LIcW)}f^hZ^u!v4CB{}Fsm_VV)Tm@5qD zjdcf6#)5f&1{ecDqyO64P9`KI#K*@&x)g(!n&@@JQ3+sRI=q{XKyeVzcDKRmK@VQGJz^%tG zO*DH_jWQ#&1@Sat zu45x^o;XwQrjLI%y5Cr6|6eo|$`MN}mI6S}%?;gTi+K0Wd__BDY6`#c=MU}03j`ox z^qWHiar!iqj*br68Cc1Nu2gRuM_H0oc5HR5WMzw#YBJ+Wsm%9p=9#t=@;89dymqau zKP+D>F!{yAmoIV_{<5;N^>;SCQP(Ui607kFI%@i(8yU7p?}*InZkocy4qt5dWs-8? zfgi_Qz*j9%K=LhN|IXK+Sz20ZXd>1-)p-pgacy h3 { + margin-top: 0; +} +.directory p { + margin: 0px; + white-space: nowrap; +} +.directory div { + display: none; + margin: 0px; +} +.directory img { + vertical-align: -30%; +} +/* these are for tree view when not used as main index */ +.directory-alt { + font-size: 100%; + font-weight: bold; +} +.directory-alt h3 { + margin: 0px; + margin-top: 1em; + font-size: 11pt; +} +.directory-alt > h3 { + margin-top: 0; +} +.directory-alt p { + margin: 0px; + white-space: nowrap; +} +.directory-alt div { + display: none; + margin: 0px; +} +.directory-alt img { + vertical-align: -30%; +} + diff --git a/doc/images/foot_bg.png b/doc/images/foot_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..b24f3a48b4e076b88cf1e802b46087ffbb3fabd5 GIT binary patch literal 173 zcmeAS@N?(olHy`uVBq!ia0vp^j6iJ9!3HE1Qx}DR1d4;)ofy`glX(f`uqAoByD;nl zL8a%*b^=8>3p^r=85p>QL70(Y)*K0-AbW|YuPgga7CvEi%SZD}LFVdux;TbNTu#2R zIsJTn+kYTPulo1Ll3AD;2!xfDmD#wLvj}>v&@k-CVP=T_%(cphxuXQAk-^i|&t;uc GLK6Ub>Mc;VkfoEM{Qf z76xHPhFNnYfP(BLp1!W^H+WcuRXD>A=l%i;Wq7(chG?8W+i%Esz<`6f_y7NWHH!5o z63R1{^0cb0vMSkFaE_%x<+7O{*?Xs&Ff%xKT5jO&GtAqcqoa~}b>qg&H07ky z617wnX2HUfTT*8{k002J-1^@s6XaB>y00001b5ch_0Itp) z=>Px#32;bRa{vGf6951U69E94oEQKA00(qQO+^RT3Fc5TrKj#zpOzs?z2=A0p@aw&u*pMn|G)ug+?Vh;0+zHe5dVLoF=!DBW&zJ?E zp99|S_j0@4%Io#|xG+sqdB5NP{rn5Cj%O+78UIn|DpKcsK9>`B2V6JsOB3oD&r;4a z{-e%Sw9ela{A&{jdu5*U-Tgr1~ zQc-ZvZCmQQrCnTG+COg8zWQ3Lv1sjNIKQPlR|Xsq_}(oT0MbDS91lPg3>xeBFKokrh3I+f~!N36>5GNtP zFb=56FWNP#oK=?bNEQ~!rtXW9X1GX=k~FL`OjOeF$R)LGB-wAZi(5xddgID^{pUl5 z$!2zVWE!iC5c%CAFL=jfl{TW1M$7Wjcr9z}SyPKmlC20r0B}G6Q80PClsJ^eQ9Rdb zuHflIJ5saoBdvW)J5o7`LrQ$sb0li*FS@pt^@$vLe#kJ^<1ntHwKLacEE8!V_VYY{ z^jk0Fa-9o0ILfhhgnDcg*`~%yk#UNgFLkMLQ1xp&>oHLMt>=krgKLZ9aoj&)eVyWk z+VI9yb#%knV zBgOT1iW^1q->g9a$6utrxv^4Ql-s5#yS%xS5>LwpJyaYZ1w-06F426k%%gi2p;YTL zn*Zi$*TU&q<}Gq{z5F%GPa}Dt9$}k233)sorIb>h&nNZ+VE-)`LNtIN1b_p=0f8Pk zSwvore6GJZg3A%uh&UU`%8A$z3ApNcvGym`^9BW5y;mkO7OVYaI#wj=im2;bzSt*w z8j*3rXhhk-tiA%ik?Y6P7yM_$}>d^Cl%aa8a!%A!J=gv)gqCjA%RAmjShY zk&jin$jBj)wrhPzE#o_v^@kLW;DnK~k;Uxogc+{8mlL+=3tKP%4hR4$2Lynj z95C}RO)LJ$<@I?2i7h|tH#%lcU&}tk-(ol0<1_foFDdWsgqjV%=7cRA;M<%KLNw4f z2!R6vT{&RuTk7799C>JZ4;!>{ThW;;ZOYD_SA%VO2ZR&C3B68;Eg1NXEf@e0gaF^` zfS#=57JO;X{zkCF($|*uL^8|wn48N;fkQ7R9I3CpA0+QRH%LYRy5b)R!`g7p731Il O0000002J-1^@s6XaB>y00001b5ch_0Itp) z=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iOZ7 z02vXk7GypE01Qq^L_t(|+U;H2a^tuXlN0^d3_%Mmt|S*_xtkl^78E$bGzMsW@O^E@B1-+ z=cVS*V0UU7pRP@U;-OvwL>DMu{R0abUb{x*}m z@B4oYcs3YunesE)Z}j8zrzRR*2Qx`$S!kAh|3>CJWrAG*GLX2Fe)h(rzjg+&sxS80 zcbMC=ef;e}EdiJ(`3%q$9hv{=thAZ zpj~02U6We1r_3JuyTgdH571c<%rJWAn5)2<+gWe7+X22Y2-~(nHi`f1_)l)SMgf#Jj8JEL5q`z9;(>xI3~8m`p)OaYtLX&lh0g*>LDJVk86(i z=kyov4*{0}DZk>FF#wNa4hGB@`||R#3~-LWl@Egki#?GMP;Am(US5vA=z0`0WFWFJQh<&3f+l(2_hZcBJtII5vSC1p*H_JX1~&q0d?50; z9+i-IF}K_8xX5gx6*IWER<=amoW`jd#YBk4T_A$cGxKdSUi@`qKv&aoVEA{nDHR+_2yWJasfY5BO>R2DM#*|ftn40E*Ih_;(Ua&ul4 zh-X-Wv9g=*`;3Z+YsQ>oJK&jZ#~tem*arhL|A(2liun{cE3;aeebuf@K z<{@$WJbN5uKxH0Oawoia%4=cqI_8 z@atR?uKZ`Gy>$Q&W$P$}#1#?lnW*=w=zGSDRyFp{|IueF%YZXbRXH<9LGqa_H^Z!7wXo>3 z0MudBnQQ0*%}I+r0dV+$PEer|sA_fDtl!x6!Mf(HQlMPz{9mChbjxs8KNzZ?j@kVX zgyaHqPMa_gx$0w97CQr!*}zAspBlr?S!q5>pR8ll>^(m=-luk6H5 zDh7cN0W_QIN=oGd$WiU)+P0b)Cm_l9kc;^F`dn2}Y{MA~M;^u`amch*0t9wHNZg== zu^-YfqhOqGZk!l}q47e8q$++sBw10+s!U&gS}Jt~@;woIxD4j_gkumL)pBM4XDLGr z0+AwHS*WfFjbnr)S`}+4Kr$r_(h)Ap|28Mea;%P1kD_P?{qED|RA;X6o!p2zi#;p$ zR7)A$dpaW?=mrNJ?W2;dq2th8uWxT}%iG)A^85GiCw~o@(#&SezeC%6Ak)sycgkj& zQI%6)ol~t%QGK9ATuK92N!ne1u>(3MO~w2;<>x7LkNVBh{3jEfI~@ZaPl&oM1(LDX zjI*Ag3M|wruZ|)&uem_Jsro$rj`x;>Af}(p>SA>ton5Xf-%SBMUq1o}`mNO36}x6% z@u?J-pVqj@1^FS2$+8HMu~S^^l-WWplxI)0s)#6)sQOdx24gKZkaEnFYgUz_Tbb4T zV6c^RNXecVUrG9qkGO5yzdj&iKnFyX%v`O-je(Cku9B~-XuY$&nv;N^O$2ur!*}w2 zGr2vc7Ds@sa(WDi$~+%w7{@eJ;eWLtx&l{6ZIh$*D$(7Y9M*RAV7R$C_x-Go=a%do zU{wj_$|@kH{ZI)(QY;r>in&mpPURU{Uad3nPKu=pRMgs%%G_65ZOjH2-F8(U87S_y zB;BCHWzdx^*;AE?Gfby^M+N+uw90JRuUkc_62LP6R3KDr(=Det<+C%)Sp7cofR!2R zfUC3rN@Xgl6pdRptk}?DRkt)Y6GU`O>umpyFX!YVs|t?`z5{?NeUNj5SAZ~AL0I{! z+6JWS<2jM@%yx8DiE^fnJDY&6f}Jj)oCLFz%w~W-7%-+=DmJQ2Xom?ExLDe!5-4;{ zrkm2K7HoBZa5kdt_EkltGmO_U&FmUimcwBa*qpdhwWTf#XnrKCCYs)P=%?EVbX z&6;N?rQ$52ciGXc>^%8^c})lt3!ojU6wsW{6x%WLdM&yE11b~CQYGE=OeJ)<5VW9y=&YuLAfe%Wnt==upw4Z%rPz^ADi2AcH>mlm8?>U_3Y8dOv1O4 zV>yC_$}i0K5rw?y;c>RjW%Bt-w4gwQ9wxN=DQW{<|6zhrfWfx7jp>?kakty;!{leS zLyF(rcHRm&GiSWMthmlMu8QMIhR;~{3KOihaf^V-(k2Bu1pNFEDW46M<`DZaJs%S^ zD0d*@=!k*`5c6b5hGIcU25)3LBw|(ivMljO=4*%&BKQ=r)OKtvDn@MT1RK?Et|-`Z zTngovW)Lfrn~6iQc6^_S%FQ7vYp`wGvcA8+KV4jwNX7TrKgt6)CM|yXVU~0;m2AAZ z$b@<0Z8rGmxaun=DydnV@JcVpHWqhsglZT044iyETcyS}V2|@r$*~aQWf13kir_}T z&tv-(1*4o9(g#N0im7WEaGCfZABR?kjnAY1>8xN=B|ZdvM?TLa_y~M%WdcR~%>2gr zLP}&LG(~0^EeOl!xi#d7BiCFXL5M2Dv6Kv|TEri;YkLH7J|})weg8xTVYNQk*}F!q zVC(LK*AXzNh#8r$9N=|CqZv?WXN;MYjaqu39=c|yS9zjUX=T%jZs!oe`3P82x zoCIb8gsR3f`)rcc z&IiudZ)HD{0aMjs&nzpb#0-vh4&}`l=o~;fU~}GACC856SOSnmxRr2J`Gv?obbyLX zbru_-pRNQ;=?7<+PAQ1kmlLA2iOYnuz^eocGZAui%=-TRF25GkVKk*?VLM7WBGYPB zT*tpuphVY?L>99zcgqh{8?J&32Rv?+%)Z_WCB$d`hC#@mKYy$$hzm4z911e3T@BQE z6}a5Q^z2GI$3(jeiH2?d26Vp6Zr1N{77{V3le;970pcooz1rBP zTCCqm&A0&u`_5tKoG#cZcARCvr+{+b_xPx#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iOZ8 z6%`IG`XD&~00I3;L_t(o!^Kz8j>8}fODg_+AF{uksuh}hxC0rJP`W$mkkHl^jB%XM z>f`Y+`Dtd>`gxwMmr`2KIk$cshdiH8N#}XW6$o}5WA?p%5)sKc%eHO9fR!#HlFUqU z&XQ6Z1~3DUmCHm#rjx?$lu|lm24QA+Eh4g2CJ}X<;QbGo;J_$CohD|s1Tu}z3KIrH zRtpQ8-Z|UyQP4caCB~&Z3QG0mMzcQLYIcQ!(sFW-xf9knF@|(?iY~XAOhHzd%#8OD2m>x}XSSsVf@;^obfpX48<@ZgFF$M4@xtjbM#B=Ojp!9yfFNKXj22K7STvD?00000NkvXXu0mjfB76)l literal 0 HcmV?d00001 diff --git a/doc/images/menu_bg_unsel.png b/doc/images/menu_bg_unsel.png new file mode 100644 index 0000000000000000000000000000000000000000..50e5fd8d3d17114bc71e14c483532f2c082920b2 GIT binary patch literal 1596 zcmV-C2E+M@P)002J-1^@s6XaB>y00001b5ch_0Itp) z=>Px#24YJ`L;wH)0002_L%V+f000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iOZ6 z5FZU~yaYY~00q2BL_t(|+U;H2a;q>5blUuRzqvD+7VvpFWE>%_)?7k5*?npguqAmd z8(5Uf*Vk7qf^ZV9(=;gx!g&NNrIcY9%E!ltq9Ht=fVI{#3`4nIuZo7C3@E%I0mpGH z+6xLIpzyv+um~ueb`dgXz*=j$TrP@=0TUO+Zl@)sQ3vT-lF8GjhNS)Y)Np5NBKI{~L@T3m#*2iTKp zt@p;JX@cbN4E2Ix|BjCFg536j+O}xz_u6Q`^V-S$v$poy&cxQ*Y0r;rMq}sMi^c@` z(Y9kbHGfVm!D!}MMH6RQOQ=PVd8HOe5K@dz0av&_)W-mG0@FVnX-$jszG6Cpd6iG4)S zD4R33W^wegr6p&!^ui1{3`424F0w(E#-nu32HZMgjcogm$jB1kn}usg#Vp4?lQGIP zmJDq6inbT557~swV%+wP_%pYAzYQ~vV>tL(hik9?P_~PVkL=oF50e?tN^s;oR77r% zlT|&VC}Ga0&tEJ%k7- zoJv4FMiNe2g8%YGh@d4{5VmDN2~q_u!NU6zP)o2N0s>_EwNi4;;4^G#zyC`L>8-YI zIsZ<;Sq-zMv%STs5t%X@*w0xZXGHX=S58L)URT4u^Pb?4-gC!@j zGZO+Nz0VPNkPS1cMpr9pK zPzLm{wVGyxy%obpDUi2TLEY1CBu?G4a_w4M*7W_50a2rjQkgL}dUVxOnilo5_XJ|? z^0b6KgUQ;2DyO!8Gi7vpi4|3GMVi2@-Nh0d2`yX4qE34Ty0;9h7ucR!#o2^lg6|D> zq>N@KDl1M!v148j=!p*!BwK!Xg({IeZ9%}cIAvzMSJ)Ae5UHkn2oMDWN!z_bn~X9f z@-5QES=A0wM|(C$1t(DkMz$~$G8#J+SMOZ5Dyv8A`w~27zqUXna*hph7O3bmD~Gp^ z*;fa=*%<}bla(WZrnkg+@^mC+kFs@kF8aj)Hk+cY4=Q!sYBVE3usz00Cd{g}$bgX+ zEs`2}PM|AlPwbhc>#U3~dKV?KArhvt%U4th9!;L?oeXA`K|g&EO%g=b=rg7ws`Z0W zfG^}&b8$*k*B4={a+xJocKjaIK(oCqTOT@;wzHCX)@Jr%Fnd8LdMj})ftln4_kht_ z?hOuGQ#w0_X^UxNi{yO`dG|z5aq*tmGIh6m-ucZ4$E=q33^;eTcR-I+1i!M{_!a$H z+wPGv{WJp#!l6$>RG?fqm4Ls$zapS;ZU)p6ES#PJ^<&F|2q=hvg78!V{`~xifWmnM uoCT@+p&H=~TxzX#9LMtc`Kf3KPscx4r4cOw-wj9r0000} +Vendor: %{?_vendorinfo:%{_vendorinfo}}%{!?_vendorinfo:The Enlightenment Project (http://www.enlightenment.org/)} +Distribution: %{?_distribution:%{_distribution}}%{!?_distribution:%{_vendor}} +Obsoletes: ecore-desktop <= 0.9.9.040 +BuildRoot: %{_tmppath}/%{name}-%{version}-root + +%description +Efreet implements the FreeDesktop.Org application and MIME-handling +standards. + +%package devel +Summary: Efreet headers, static libraries, documentation and test programs +Group: System Environment/Libraries +Requires: %{name} = %{version} + +%description devel +Headers, static libraries, test programs and documentation for Efreet + +%prep +%setup -q + +%build +%{configure} --prefix=%{_prefix} +%{__make} %{?_smp_mflags} %{?mflags} + +%install +%{__make} %{?mflags_install} DESTDIR=$RPM_BUILD_ROOT install + +# Get rid of unneeded testing cruft. +%{__rm} -rf $RPM_BUILD_ROOT%{_datadir}/%{name} + +%clean +test "x$RPM_BUILD_ROOT" != "x/" && rm -rf $RPM_BUILD_ROOT + +%post +/sbin/ldconfig + +%postun +/sbin/ldconfig + +%files +%defattr(-, root, root) +%doc AUTHORS COPYING* README +%{_bindir}/%{name}* +%{_libdir}/*.so.* +%{_libdir}/efreet/ +%{_datadir}/locale/*/LC_MESSAGES/*.mo + +%files devel +%defattr(-, root, root) +%{_includedir}/%{name}-1 +%{_libdir}/*.so +%{_libdir}/*.la +%{_libdir}/*.a +%{_libdir}/pkgconfig/* + +%changelog diff --git a/m4/ac_attribute.m4 b/m4/ac_attribute.m4 new file mode 100644 index 0000000..23479a9 --- /dev/null +++ b/m4/ac_attribute.m4 @@ -0,0 +1,47 @@ +dnl Copyright (C) 2004-2008 Kim Woelders +dnl Copyright (C) 2008 Vincent Torri +dnl That code is public domain and can be freely used or copied. +dnl Originally snatched from somewhere... + +dnl Macro for checking if the compiler supports __attribute__ + +dnl Usage: AC_C___ATTRIBUTE__ +dnl call AC_DEFINE for HAVE___ATTRIBUTE__ and __UNUSED__ +dnl if the compiler supports __attribute__, HAVE___ATTRIBUTE__ is +dnl defined to 1 and __UNUSED__ is defined to __attribute__((unused)) +dnl otherwise, HAVE___ATTRIBUTE__ is not defined and __UNUSED__ is +dnl defined to nothing. + +AC_DEFUN([AC_C___ATTRIBUTE__], +[ + +AC_MSG_CHECKING([for __attribute__]) + +AC_CACHE_VAL([ac_cv___attribute__], + [AC_TRY_COMPILE( + [ +#include + +int func(int x); +int foo(int x __attribute__ ((unused))) +{ + exit(1); +} + ], + [], + [ac_cv___attribute__="yes"], + [ac_cv___attribute__="no"] + )]) + +AC_MSG_RESULT($ac_cv___attribute__) + +if test "x${ac_cv___attribute__}" = "xyes" ; then + AC_DEFINE([HAVE___ATTRIBUTE__], [1], [Define to 1 if your compiler has __attribute__]) + AC_DEFINE([__UNUSED__], [__attribute__((unused))], [Macro declaring a function argument to be unused]) + else + AC_DEFINE([__UNUSED__], [], [Macro declaring a function argument to be unused]) +fi + +]) + +dnl End of ac_attribute.m4 diff --git a/m4/ac_define_dir.m4 b/m4/ac_define_dir.m4 new file mode 100644 index 0000000..f3d8734 --- /dev/null +++ b/m4/ac_define_dir.m4 @@ -0,0 +1,47 @@ +##### http://autoconf-archive.cryp.to/ac_define_dir.html +# +# SYNOPSIS +# +# AC_DEFINE_DIR(VARNAME, DIR [, DESCRIPTION]) +# +# DESCRIPTION +# +# This macro sets VARNAME to the expansion of the DIR variable, +# taking care of fixing up ${prefix} and such. +# +# VARNAME is then offered as both an output variable and a C +# preprocessor symbol. +# +# Example: +# +# AC_DEFINE_DIR([DATADIR], [datadir], [Where data are placed to.]) +# +# LAST MODIFICATION +# +# 2006-10-13 +# +# COPYLEFT +# +# Copyright (c) 2006 Stepan Kasal +# Copyright (c) 2006 Andreas Schwab +# Copyright (c) 2006 Guido U. Draheim +# Copyright (c) 2006 Alexandre Oliva +# +# Copying and distribution of this file, with or without +# modification, are permitted in any medium without royalty provided +# the copyright notice and this notice are preserved. + +AC_DEFUN([AC_DEFINE_DIR], [ + prefix_NONE= + exec_prefix_NONE= + test "x$prefix" = xNONE && prefix_NONE=yes && prefix=$ac_default_prefix + test "x$exec_prefix" = xNONE && exec_prefix_NONE=yes && exec_prefix=$prefix +dnl In Autoconf 2.60, ${datadir} refers to ${datarootdir}, which in turn +dnl refers to ${prefix}. Thus we have to use `eval' twice. + eval ac_define_dir="\"[$]$2\"" + eval ac_define_dir="\"$ac_define_dir\"" + AC_SUBST($1, "$ac_define_dir") + AC_DEFINE_UNQUOTED($1, "$ac_define_dir", [$3]) + test "$prefix_NONE" && prefix=NONE + test "$exec_prefix_NONE" && exec_prefix=NONE +]) diff --git a/m4/efl_compiler_flag.m4 b/m4/efl_compiler_flag.m4 new file mode 100644 index 0000000..618c6a6 --- /dev/null +++ b/m4/efl_compiler_flag.m4 @@ -0,0 +1,57 @@ +dnl Copyright (C) 2010 Vincent Torri +dnl and Albin Tonnerre +dnl That code is public domain and can be freely used or copied. + +dnl Macro that checks if a compiler flag is supported by the compiler. + +dnl Usage: EFL_COMPILER_FLAG(flag) +dnl flag is added to CFLAGS if supported. + +AC_DEFUN([EFL_COMPILER_FLAG], +[ + +CFLAGS_save="${CFLAGS}" +CFLAGS="${CFLAGS} $1" + +AC_LANG_PUSH([C]) +AC_MSG_CHECKING([whether the compiler supports $1]) + +AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([[]])], + [have_flag="yes"], + [have_flag="no"]) +AC_MSG_RESULT([${have_flag}]) + +if test "x${have_flag}" = "xno" ; then + CFLAGS="${CFLAGS_save}" +fi +AC_LANG_POP([C]) + +]) + +dnl Macro that checks if a linker flag is supported by the compiler. + +dnl Usage: EFL_LINKER_FLAG(flag) +dnl flag is added to CFLAGS if supported (will be passed to ld anyway). + +AC_DEFUN([EFL_LINKER_FLAG], +[ + +CFLAGS_save="${CFLAGS}" +CFLAGS="${CFLAGS} $1" + +AC_LANG_PUSH([C]) +AC_MSG_CHECKING([whether the compiler supports $1]) + +AC_LINK_IFELSE( + [AC_LANG_PROGRAM([[]])], + [have_flag="yes"], + [have_flag="no"]) +AC_MSG_RESULT([${have_flag}]) + +if test "x${have_flag}" = "xno" ; then + CFLAGS="${CFLAGS_save}" +fi +AC_LANG_POP([C]) + +]) diff --git a/m4/efl_coverage.m4 b/m4/efl_coverage.m4 new file mode 100644 index 0000000..85d0321 --- /dev/null +++ b/m4/efl_coverage.m4 @@ -0,0 +1,62 @@ +dnl Copyright (C) 2008 Vincent Torri +dnl That code is public domain and can be freely used or copied. + +dnl Macro that check if coverage support is wanted and, if yes, if +dnl lcov is available. + +dnl Usage: EFL_CHECK_COVERAGE(tests [, ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]) +dnl The parameter 'tests' is used if a dependency is needed. If set to "yes", +dnl the dependency is available. +dnl Defines EFL_COVERAGE_CFLAGS and EFL_COVERAGE_LIBS variables +dnl Defines the automake conditionnal EFL_ENABLE_COVERAGE + +AC_DEFUN([EFL_CHECK_COVERAGE], +[ + +dnl configure option + +AC_ARG_ENABLE([coverage], + [AC_HELP_STRING([--enable-coverage], [enable coverage profiling instrumentation @<:@default=disabled@:>@])], + [ + if test "x${enableval}" = "xyes" ; then + _efl_enable_coverage="yes" + else + _efl_enable_coverage="no" + fi + ], + [_efl_enable_coverage="no"]) + +AC_MSG_CHECKING([whether to use profiling instrumentation]) +AC_MSG_RESULT([$_efl_enable_coverage]) + +dnl lcov check + +if test "x$_efl_enable_coverage" = "xyes" && test ! "x$1" = "xyes" ; then + AC_MSG_WARN([Coverage report requested but tests not being built, disable profiling instrumentation.]) + AC_MSG_WARN([Run configure with --enable-tests]) + _efl_enable_coverage="no" +fi + +if test "x$_efl_enable_coverage" = "xyes" ; then + AC_CHECK_PROG(have_lcov, [lcov], [yes], [no]) + if test "x$have_lcov" = "xyes" ; then + EFL_COVERAGE_CFLAGS="-fprofile-arcs -ftest-coverage" + EFL_COVERAGE_LIBS="-lgcov" +# remove any optimisation flag and force debug symbols + EFL_DEBUG_CFLAGS="-g -O0 -DDEBUG" + else + AC_MSG_WARN([lcov is not found, disable profiling instrumentation]) + _efl_enable_coverage="no" + fi +fi + +dnl Substitution +AC_SUBST(EFL_COVERAGE_CFLAGS) +AC_SUBST(EFL_COVERAGE_LIBS) + +AM_CONDITIONAL(EFL_ENABLE_COVERAGE, test "x${_efl_enable_coverage}" = "xyes") + +AS_IF([test "x$_efl_enable_coverage" = "xyes"], [$2], [$3]) +]) + +dnl End of efl_coverage.m4 diff --git a/m4/efl_doxygen.m4 b/m4/efl_doxygen.m4 new file mode 100644 index 0000000..d83ed68 --- /dev/null +++ b/m4/efl_doxygen.m4 @@ -0,0 +1,97 @@ +dnl Copyright (C) 2008 Vincent Torri +dnl That code is public domain and can be freely used or copied. + +dnl Macro that check if doxygen is available or not. + +dnl EFL_CHECK_DOXYGEN([ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]) +dnl Test for the doxygen program +dnl Defines efl_doxygen +dnl Defines the automake conditionnal EFL_BUILD_DOC +dnl +AC_DEFUN([EFL_CHECK_DOXYGEN], +[ + +dnl +dnl Disable the build of the documentation +dnl +AC_ARG_ENABLE([doc], + [AC_HELP_STRING( + [--disable-doc], + [Disable documentation build @<:@default=enabled@:>@])], + [ + if test "x${enableval}" = "xyes" ; then + efl_enable_doc="yes" + else + efl_enable_doc="no" + fi + ], + [efl_enable_doc="yes"]) + +AC_MSG_CHECKING([whether to build documentation]) +AC_MSG_RESULT([${efl_enable_doc}]) + +if test "x${efl_enable_doc}" = "xyes" ; then + +dnl Specify the file name, without path + + efl_doxygen="doxygen" + + AC_ARG_WITH([doxygen], + [AC_HELP_STRING( + [--with-doxygen=FILE], + [doxygen program to use @<:@default=doxygen@:>@])], + +dnl Check the given doxygen program. + + [efl_doxygen=${withval} + AC_CHECK_PROG([efl_have_doxygen], + [${efl_doxygen}], + [yes], + [no]) + if test "x${efl_have_doxygen}" = "xno" ; then + echo "WARNING:" + echo "The doxygen program you specified:" + echo "${efl_doxygen}" + echo "was not found. Please check the path and make sure " + echo "the program exists and is executable." + AC_MSG_WARN([no doxygen detected. Documentation will not be built]) + fi + ], + [AC_CHECK_PROG([efl_have_doxygen], + [${efl_doxygen}], + [yes], + [no]) + if test "x${efl_have_doxygen}" = "xno" ; then + echo "WARNING:" + echo "The doxygen program was not found in your execute path." + echo "You may have doxygen installed somewhere not covered by your path." + echo "" + echo "If this is the case make sure you have the packages installed, AND" + echo "that the doxygen program is in your execute path (see your" + echo "shell manual page on setting the \$PATH environment variable), OR" + echo "alternatively, specify the program to use with --with-doxygen." + AC_MSG_WARN([no doxygen detected. Documentation will not be built]) + fi + ]) +fi + +dnl +dnl Substitution +dnl +AC_SUBST([efl_doxygen]) + +if ! test "x${efl_have_doxygen}" = "xyes" ; then + efl_enable_doc="no" +fi + +AM_CONDITIONAL(EFL_BUILD_DOC, test "x${efl_enable_doc}" = "xyes") + +if test "x${efl_enable_doc}" = "xyes" ; then + m4_default([$1], [:]) +else + m4_default([$2], [:]) +fi + +]) + +dnl End of efl_doxygen.m4 diff --git a/m4/efl_path_max.m4 b/m4/efl_path_max.m4 new file mode 100644 index 0000000..3ac29bd --- /dev/null +++ b/m4/efl_path_max.m4 @@ -0,0 +1,37 @@ +dnl Check for PATH_MAX in limits.h, and define a default value if not found +dnl This is a workaround for systems not providing PATH_MAX, like GNU/Hurd + +dnl EFL_CHECK_PATH_MAX([DEFAULT_VALUE_IF_NOT_FOUND]) +dnl +dnl If PATH_MAX is not defined in , defines it +dnl to DEFAULT_VALUE_IF_NOT_FOUND if it exists, or fallback +dnl to using 4096 + +AC_DEFUN([EFL_CHECK_PATH_MAX], +[ + +default_max=m4_default([$1], "4096") + +AC_LANG_PUSH([C]) + +AC_MSG_CHECKING([for PATH_MAX in limits.h]) +AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM( + [[ +#include + ]], + [[ +int i = PATH_MAX; + ]])], + [AC_MSG_RESULT([yes])], + [ + AC_DEFINE_UNQUOTED([PATH_MAX], + [${default_max}], + [default value since PATH_MAX is not defined]) + AC_MSG_RESULT([no: using ${default_max}]) + ]) + +AC_LANG_POP([C]) + +]) +dnl end of efl_path_max.m4 diff --git a/m4/efl_tests.m4 b/m4/efl_tests.m4 new file mode 100644 index 0000000..3a4dfe2 --- /dev/null +++ b/m4/efl_tests.m4 @@ -0,0 +1,43 @@ +dnl Copyright (C) 2008 Vincent Torri +dnl That code is public domain and can be freely used or copied. + +dnl Macro that check if tests programs are wanted and if yes, if +dnl the Check library is available. + +dnl Usage: EFL_CHECK_TESTS([ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]) +dnl Define the automake conditionnal EFL_ENABLE_TESTS + +AC_DEFUN([EFL_CHECK_TESTS], +[ + +dnl configure option + +AC_ARG_ENABLE([tests], + [AC_HELP_STRING([--enable-tests], [enable tests @<:@default=disabled@:>@])], + [ + if test "x${enableval}" = "xyes" ; then + _efl_enable_tests="yes" + else + _efl_enable_tests="no" + fi + ], + [_efl_enable_tests="no"]) + +AC_MSG_CHECKING([whether tests are built]) +AC_MSG_RESULT([${_efl_enable_tests}]) + +AC_REQUIRE([PKG_PROG_PKG_CONFIG]) + +if test "x${_efl_enable_tests}" = "xyes" ; then + PKG_CHECK_MODULES([CHECK], + [check >= 0.9.5], + [dummy="yes"], + [_efl_enable_tests="no"]) +fi + +AM_CONDITIONAL(EFL_ENABLE_TESTS, test "x${_efl_enable_tests}" = "xyes") + +AS_IF([test "x$_efl_enable_tests" = "xyes"], [$1], [$2]) +]) + +dnl End of efl_tests.m4 diff --git a/po/LINGUAS b/po/LINGUAS new file mode 100644 index 0000000..6e30c56 --- /dev/null +++ b/po/LINGUAS @@ -0,0 +1,12 @@ +cs +de +el +es +fr +it +ja +ko +nl +pt +ru +sl diff --git a/po/Makevars b/po/Makevars new file mode 100644 index 0000000..22837ab --- /dev/null +++ b/po/Makevars @@ -0,0 +1,41 @@ +# Makefile variables for PO directory in any package using GNU gettext. + +# Usually the message domain is the same as the package name. +DOMAIN = $(PACKAGE) + +# These two variables depend on the location of this directory. +subdir = po +top_builddir = .. + +# These options get passed to xgettext. +XGETTEXT_OPTIONS = --keyword=_ --keyword=N_ --from-code=UTF-8 --foreign-user + +# This is the copyright holder that gets inserted into the header of the +# $(DOMAIN).pot file. Set this to the copyright holder of the surrounding +# package. (Note that the msgstr strings, extracted from the package's +# sources, belong to the copyright holder of the package.) Translators are +# expected to transfer the copyright for their translations to this person +# or entity, or to disclaim their copyright. The empty string stands for +# the public domain; in this case the translators are expected to disclaim +# their copyright. +COPYRIGHT_HOLDER = Enlightenment development team + +# This is the email address or URL to which the translators shall report +# bugs in the untranslated strings: +# - Strings which are not entire sentences, see the maintainer guidelines +# in the GNU gettext documentation, section 'Preparing Strings'. +# - Strings which use unclear terms or require additional context to be +# understood. +# - Strings which make invalid assumptions about notation of date, time or +# money. +# - Pluralisation problems. +# - Incorrect English spelling. +# - Incorrect formatting. +# It can be your email address, or a mailing list address where translators +# can write to without being subscribed, or the URL of a web page through +# which the translators can contact you. +MSGID_BUGS_ADDRESS = enlightenment-devel@lists.sourceforge.net + +# This is the list of locale categories, beyond LC_MESSAGES, for which the +# message catalogs shall be used. It is usually empty. +EXTRA_LOCALE_CATEGORIES = diff --git a/po/POTFILES.in b/po/POTFILES.in new file mode 100644 index 0000000..68cb9d8 --- /dev/null +++ b/po/POTFILES.in @@ -0,0 +1 @@ +src/lib/efreet_base.c diff --git a/po/cs.po b/po/cs.po new file mode 100644 index 0000000..b82c806 --- /dev/null +++ b/po/cs.po @@ -0,0 +1,20 @@ +# Czech translation for Efreet. +# Copyright (C) 2012 Enlightenment development team +# This file is put in the public domain. +# +msgid "" +msgstr "" +"Project-Id-Version: Efreet\n" +"Report-Msgid-Bugs-To: enlightenment-devel@lists.sourceforge.net\n" +"POT-Creation-Date: 2012-08-27 19:16+0900\n" +"PO-Revision-Date: 2012-06-24 17:10+0900\n" +"Last-Translator: Jerome Pinot \n" +"Language-Team: Enlightenment Team\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/lib/efreet_base.c:127 +msgid "Desktop" +msgstr "Plocha" diff --git a/po/de.po b/po/de.po new file mode 100644 index 0000000..20395bd --- /dev/null +++ b/po/de.po @@ -0,0 +1,20 @@ +# German translation for Efreet. +# Copyright (C) 2012 Enlightenment development team +# This file is put in the public domain. +# +msgid "" +msgstr "" +"Project-Id-Version: Efreet\n" +"Report-Msgid-Bugs-To: enlightenment-devel@lists.sourceforge.net\n" +"POT-Creation-Date: 2012-08-27 19:16+0900\n" +"PO-Revision-Date: 2012-06-24 17:10+0900\n" +"Last-Translator: Jerome Pinot \n" +"Language-Team: Enlightenment Team\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/lib/efreet_base.c:127 +msgid "Desktop" +msgstr "Arbeitsfläche" diff --git a/po/efreet.pot b/po/efreet.pot new file mode 100644 index 0000000..23abe0b --- /dev/null +++ b/po/efreet.pot @@ -0,0 +1,22 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR Enlightenment development team +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: efreet 1.7.0\n" +"Report-Msgid-Bugs-To: enlightenment-devel@lists.sourceforge.net\n" +"POT-Creation-Date: 2012-08-27 19:16+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/lib/efreet_base.c:127 +msgid "Desktop" +msgstr "" diff --git a/po/el.po b/po/el.po new file mode 100644 index 0000000..5c6ac3c --- /dev/null +++ b/po/el.po @@ -0,0 +1,20 @@ +# Greek translation for Efreet. +# Copyright (C) 2012 Enlightenment development team +# This file is put in the public domain. +# +msgid "" +msgstr "" +"Project-Id-Version: Efreet\n" +"Report-Msgid-Bugs-To: enlightenment-devel@lists.sourceforge.net\n" +"POT-Creation-Date: 2012-08-27 19:16+0900\n" +"PO-Revision-Date: 2012-06-24 17:10+0900\n" +"Last-Translator: Jerome Pinot \n" +"Language-Team: Enlightenment Team\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/lib/efreet_base.c:127 +msgid "Desktop" +msgstr "Επιφάνεια εργασίας" diff --git a/po/es.po b/po/es.po new file mode 100644 index 0000000..5fdf7d8 --- /dev/null +++ b/po/es.po @@ -0,0 +1,20 @@ +# Spanish translation for Efreet. +# Copyright (C) 2012 Enlightenment development team +# This file is put in the public domain. +# +msgid "" +msgstr "" +"Project-Id-Version: Efreet\n" +"Report-Msgid-Bugs-To: enlightenment-devel@lists.sourceforge.net\n" +"POT-Creation-Date: 2012-08-27 19:16+0900\n" +"PO-Revision-Date: 2012-06-24 17:10+0900\n" +"Last-Translator: Jerome Pinot \n" +"Language-Team: Enlightenment Team\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/lib/efreet_base.c:127 +msgid "Desktop" +msgstr "Escritorio" diff --git a/po/fr.po b/po/fr.po new file mode 100644 index 0000000..1edbecc --- /dev/null +++ b/po/fr.po @@ -0,0 +1,20 @@ +# French translation for Efreet. +# Copyright (C) 2012 Enlightenment development team +# This file is put in the public domain. +# +msgid "" +msgstr "" +"Project-Id-Version: Efreet\n" +"Report-Msgid-Bugs-To: enlightenment-devel@lists.sourceforge.net\n" +"POT-Creation-Date: 2012-08-27 19:16+0900\n" +"PO-Revision-Date: 2012-06-24 17:10+0900\n" +"Last-Translator: Jerome Pinot \n" +"Language-Team: Enlightenment Team\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/lib/efreet_base.c:127 +msgid "Desktop" +msgstr "Bureau" diff --git a/po/it.po b/po/it.po new file mode 100644 index 0000000..89cc64c --- /dev/null +++ b/po/it.po @@ -0,0 +1,20 @@ +# Italian translation for Efreet. +# Copyright (C) 2012 Enlightenment development team +# This file is put in the public domain. +# +msgid "" +msgstr "" +"Project-Id-Version: Efreet\n" +"Report-Msgid-Bugs-To: enlightenment-devel@lists.sourceforge.net\n" +"POT-Creation-Date: 2012-08-27 19:16+0900\n" +"PO-Revision-Date: 2012-06-24 17:10+0900\n" +"Last-Translator: Jerome Pinot \n" +"Language-Team: Enlightenment Team\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/lib/efreet_base.c:127 +msgid "Desktop" +msgstr "Scrivania" diff --git a/po/ja.po b/po/ja.po new file mode 100644 index 0000000..5aacd28 --- /dev/null +++ b/po/ja.po @@ -0,0 +1,20 @@ +# Japanese translation for Efreet. +# Copyright (C) 2012 Enlightenment development team +# This file is put in the public domain. +# +msgid "" +msgstr "" +"Project-Id-Version: Efreet\n" +"Report-Msgid-Bugs-To: enlightenment-devel@lists.sourceforge.net\n" +"POT-Creation-Date: 2012-08-27 19:16+0900\n" +"PO-Revision-Date: 2012-06-24 17:10+0900\n" +"Last-Translator: Jerome Pinot \n" +"Language-Team: Enlightenment Team\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/lib/efreet_base.c:127 +msgid "Desktop" +msgstr "デスクトップ" diff --git a/po/ko.po b/po/ko.po new file mode 100644 index 0000000..6d4e439 --- /dev/null +++ b/po/ko.po @@ -0,0 +1,20 @@ +# Korean translation for Efreet. +# Copyright (C) 2012 Enlightenment development team +# This file is put in the public domain. +# +msgid "" +msgstr "" +"Project-Id-Version: Efreet\n" +"Report-Msgid-Bugs-To: enlightenment-devel@lists.sourceforge.net\n" +"POT-Creation-Date: 2012-08-27 19:16+0900\n" +"PO-Revision-Date: 2012-06-24 17:10+0900\n" +"Last-Translator: Jerome Pinot \n" +"Language-Team: Enlightenment Team\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/lib/efreet_base.c:127 +msgid "Desktop" +msgstr "바탕화면" diff --git a/po/nl.po b/po/nl.po new file mode 100644 index 0000000..c88443c --- /dev/null +++ b/po/nl.po @@ -0,0 +1,20 @@ +# Dutch translation for Efreet. +# Copyright (C) 2012 Enlightenment development team +# This file is put in the public domain. +# +msgid "" +msgstr "" +"Project-Id-Version: Efreet\n" +"Report-Msgid-Bugs-To: enlightenment-devel@lists.sourceforge.net\n" +"POT-Creation-Date: 2012-08-27 19:16+0900\n" +"PO-Revision-Date: 2012-06-24 17:10+0900\n" +"Last-Translator: Jerome Pinot \n" +"Language-Team: Enlightenment Team\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/lib/efreet_base.c:127 +msgid "Desktop" +msgstr "Bureaublad" diff --git a/po/pt.po b/po/pt.po new file mode 100644 index 0000000..59e2827 --- /dev/null +++ b/po/pt.po @@ -0,0 +1,20 @@ +# Portuguese translation for Efreet. +# Copyright (C) 2012 Enlightenment development team +# This file is put in the public domain. +# +msgid "" +msgstr "" +"Project-Id-Version: Efreet\n" +"Report-Msgid-Bugs-To: enlightenment-devel@lists.sourceforge.net\n" +"POT-Creation-Date: 2012-08-27 19:16+0900\n" +"PO-Revision-Date: 2012-06-24 17:10+0900\n" +"Last-Translator: Jerome Pinot \n" +"Language-Team: Enlightenment Team\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/lib/efreet_base.c:127 +msgid "Desktop" +msgstr "Área de Trabalho" diff --git a/po/ru.po b/po/ru.po new file mode 100644 index 0000000..442bec1 --- /dev/null +++ b/po/ru.po @@ -0,0 +1,20 @@ +# Russian translation for Efreet. +# Copyright (C) 2012 Enlightenment development team +# This file is put in the public domain. +# +msgid "" +msgstr "" +"Project-Id-Version: Efreet\n" +"Report-Msgid-Bugs-To: enlightenment-devel@lists.sourceforge.net\n" +"POT-Creation-Date: 2012-08-27 19:16+0900\n" +"PO-Revision-Date: 2012-06-24 17:10+0900\n" +"Last-Translator: Jerome Pinot \n" +"Language-Team: Enlightenment Team\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/lib/efreet_base.c:127 +msgid "Desktop" +msgstr "Рабочий стол" diff --git a/po/sl.po b/po/sl.po new file mode 100644 index 0000000..a5c6772 --- /dev/null +++ b/po/sl.po @@ -0,0 +1,20 @@ +# Slovenian translation for Efreet. +# Copyright (C) 2012 Enlightenment development team +# This file is put in the public domain. +# +msgid "" +msgstr "" +"Project-Id-Version: Efreet\n" +"Report-Msgid-Bugs-To: enlightenment-devel@lists.sourceforge.net\n" +"POT-Creation-Date: 2012-08-27 19:16+0900\n" +"PO-Revision-Date: 2012-06-24 17:10+0900\n" +"Last-Translator: Jerome Pinot \n" +"Language-Team: Enlightenment Team\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/lib/efreet_base.c:127 +msgid "Desktop" +msgstr "Namizje" diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..add035f --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,4 @@ + +SUBDIRS = lib bin tests + +MAINTAINERCLEANFILES = Makefile.in diff --git a/src/bin/Makefile.am b/src/bin/Makefile.am new file mode 100644 index 0000000..3040a20 --- /dev/null +++ b/src/bin/Makefile.am @@ -0,0 +1,29 @@ + +MAINTAINERCLEANFILES = Makefile.in + +AM_CPPFLAGS = \ +-I. \ +-I$(top_srcdir)/src/lib \ +-DPACKAGE_BIN_DIR=\"$(bindir)\" \ +-DPACKAGE_LIB_DIR=\"$(libdir)\" \ +-DPACKAGE_DATA_DIR=\"$(datadir)\" \ +@EFREET_CFLAGS@ + +internal_bindir=$(libdir)/efreet +internal_bin_PROGRAMS = \ +efreet_desktop_cache_create \ +efreet_icon_cache_create + +efreet_desktop_cache_create_LDADD = \ +$(top_builddir)/src/lib/libefreet.la \ +@EFREET_LIBS@ + +efreet_desktop_cache_create_SOURCES = \ +efreet_desktop_cache_create.c + +efreet_icon_cache_create_LDADD = \ +$(top_builddir)/src/lib/libefreet.la \ +@EFREET_LIBS@ + +efreet_icon_cache_create_SOURCES = \ +efreet_icon_cache_create.c diff --git a/src/bin/efreet_desktop_cache_create.c b/src/bin/efreet_desktop_cache_create.c new file mode 100644 index 0000000..c397022 --- /dev/null +++ b/src/bin/efreet_desktop_cache_create.c @@ -0,0 +1,541 @@ +#ifdef HAVE_CONFIG_H +# include +#endif +#include +#include +#include +#include + +#include +#include +#include +#include + +#define EFREET_MODULE_LOG_DOM _efreet_desktop_cache_log_dom +static int _efreet_desktop_cache_log_dom = -1; + +#include "Efreet.h" +#include "efreet_private.h" +#include "efreet_cache_private.h" + +static Eet_Data_Descriptor *edd = NULL; +static Eet_File *ef = NULL; +static Eet_File *util_ef = NULL; + +static Eina_Hash *desktops = NULL; + +static Eina_Hash *file_ids = NULL; +static Efreet_Cache_Hash *old_file_ids = NULL; +static Eina_Hash *paths = NULL; + +static Eina_Hash *mime_types = NULL; +static Eina_Hash *categories = NULL; +static Eina_Hash *startup_wm_class = NULL; +static Eina_Hash *name = NULL; +static Eina_Hash *generic_name = NULL; +static Eina_Hash *comment = NULL; +static Eina_Hash *exec = NULL; + +static int +strcmplen(const void *data1, const void *data2) +{ + return strncmp(data1, data2, eina_stringshare_strlen(data1)); +} + +static int +cache_add(const char *path, const char *file_id, int priority __UNUSED__, int *changed) +{ + Efreet_Desktop *desk; + char *ext; + + INF("FOUND: %s", path); + if (file_id) INF(" (id): %s", file_id); + ext = strrchr(path, '.'); + if (!ext || (strcmp(ext, ".desktop") && strcmp(ext, ".directory"))) return 1; + desk = efreet_desktop_new(path); + if (desk) INF(" OK"); + else INF(" FAIL"); + if (!desk) return 1; + if (!desk->eet) + { + /* This file isn't in cache */ + *changed = 1; + INF(" NEW"); + } + else if (ecore_file_mod_time(desk->orig_path) != desk->load_time) + { + efreet_desktop_free(desk); + *changed = 1; + desk = efreet_desktop_uncached_new(path); + if (desk) INF(" CHANGED"); + else INF(" NO UNCACHED"); + } + if (!desk) return 1; + if (file_id && old_file_ids && !eina_hash_find(old_file_ids->hash, file_id)) + { + *changed = 1; + INF(" NOT IN UTILS"); + } + if (!eina_hash_find(paths, desk->orig_path)) + { + if (!eet_data_write(ef, edd, desk->orig_path, desk, 0)) + return 0; + eina_hash_add(paths, desk->orig_path, (void *)1); + } + /* TODO: We should check priority, and not just hope we search in right order */ + /* TODO: We need to find out if prioritized file id has changed because of + * changed search order. */ + if (!desk->hidden && desk->type == EFREET_DESKTOP_TYPE_APPLICATION && + file_id && !eina_hash_find(file_ids, file_id)) + { + Eina_List *l; + char *data; + Efreet_Cache_Array_String *array; + +#define ADD_LIST(list, hash) \ + EINA_LIST_FOREACH((list), l, data) \ + { \ + array = eina_hash_find((hash), data); \ + if (!array) \ + array = NEW(Efreet_Cache_Array_String, 1); \ + array->array = realloc(array->array, sizeof (char *) * (array->array_count + 1)); \ + array->array[array->array_count++] = desk->orig_path; \ + eina_hash_set((hash), data, array); \ + } +#define ADD_ELEM(elem, hash) \ + if ((elem)) \ + { \ + data = (elem); \ + array = eina_hash_find((hash), data); \ + if (!array) \ + array = NEW(Efreet_Cache_Array_String, 1); \ + array->array = realloc(array->array, sizeof (char *) * (array->array_count + 1)); \ + array->array[array->array_count++] = desk->orig_path; \ + eina_hash_set((hash), data, array); \ + } + ADD_LIST(desk->mime_types, mime_types); + ADD_LIST(desk->categories, categories); + ADD_ELEM(desk->startup_wm_class, startup_wm_class); + ADD_ELEM(desk->name, name); + ADD_ELEM(desk->generic_name, generic_name); + ADD_ELEM(desk->comment, comment); + ADD_ELEM(desk->exec, exec); + eina_hash_add(file_ids, file_id, desk->orig_path); + eina_hash_add(desktops, desk->orig_path, desk); + } + else + efreet_desktop_free(desk); + return 1; +} + + +static int +cache_scan(const char *path, const char *base_id, int priority, int recurse, int *changed) +{ + char *file_id = NULL; + char id[PATH_MAX]; + char buf[PATH_MAX]; + Eina_Iterator *it; + Eina_File_Direct_Info *info; + + if (!ecore_file_is_dir(path)) return 1; + + it = eina_file_direct_ls(path); + if (!it) return 1; + + id[0] = '\0'; + EINA_ITERATOR_FOREACH(it, info) + { + const char *fname; + + fname = info->path + info->name_start; + if (base_id) + { + if (*base_id) + snprintf(id, sizeof(id), "%s-%s", base_id, fname); + else + strcpy(id, fname); + file_id = id; + } + + snprintf(buf, sizeof(buf), "%s/%s", path, fname); + if (ecore_file_is_dir(buf)) + { + if (recurse) + cache_scan(buf, file_id, priority, recurse, changed); + } + else + { + if (!cache_add(buf, file_id, priority, changed)) + { + eina_iterator_free(it); + return 0; + } + } + } + eina_iterator_free(it); + return 1; +} + +static int +cache_lock_file(void) +{ + char file[PATH_MAX]; + struct flock fl; + int lockfd; + + snprintf(file, sizeof(file), "%s/efreet/desktop_data.lock", efreet_cache_home_get()); + lockfd = open(file, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); + if (lockfd < 0) return -1; + efreet_fsetowner(lockfd); + + memset(&fl, 0, sizeof(struct flock)); + fl.l_type = F_WRLCK; + fl.l_whence = SEEK_SET; + if (fcntl(lockfd, F_SETLK, &fl) < 0) + { + INF("LOCKED! You may want to delete %s if this persists", file); + close(lockfd); + return -1; + } + + return lockfd; +} + +int +main(int argc, char **argv) +{ + /* TODO: + * - Add file monitor on files, so that we catch changes on files + * during whilst this program runs. + * - Maybe linger for a while to reduce number of cache re-creates. + */ + Efreet_Cache_Hash hash; + Efreet_Cache_Version version; + Eina_List *dirs = NULL; + Eina_List *systemdirs = NULL; + Efreet_Cache_Array_String *user_dirs = NULL; + Eina_List *extra_dirs = NULL; + Eina_List *store_dirs = NULL; + int priority = 0; + char *dir = NULL; + char *path; + int lockfd = -1, tmpfd; + int changed = 0; + int i; + char file[PATH_MAX] = { '\0' }; + char util_file[PATH_MAX] = { '\0' }; + + if (!eina_init()) goto eina_error; + _efreet_desktop_cache_log_dom = + eina_log_domain_register("efreet_desktop_cache", EFREET_DEFAULT_LOG_COLOR); + if (_efreet_desktop_cache_log_dom < 0) + { + EINA_LOG_ERR("Efreet: Could not create a log domain for efreet_desktop_cache."); + return -1; + } + + for (i = 1; i < argc; i++) + { + if (!strcmp(argv[i], "-v")) + eina_log_domain_level_set("efreet_desktop_cache", EINA_LOG_LEVEL_DBG); + else if ((!strcmp(argv[i], "-h")) || + (!strcmp(argv[i], "-help")) || + (!strcmp(argv[i], "--h")) || + (!strcmp(argv[i], "--help"))) + { + printf("Options:\n"); + printf(" -v Verbose mode\n"); + printf(" -d dir1 dir2 Extra dirs\n"); + exit(0); + } + else if (!strcmp(argv[i], "-d")) + { + while ((i < (argc - 1)) && (argv[(i + 1)][0] != '-')) + extra_dirs = eina_list_append(extra_dirs, argv[++i]); + } + } + extra_dirs = eina_list_sort(extra_dirs, -1, EINA_COMPARE_CB(strcmp)); + + /* init external subsystems */ + if (!eet_init()) goto eet_error; + if (!ecore_init()) goto ecore_error; + + efreet_cache_update = 0; + /* finish efreet init */ + if (!efreet_init()) goto efreet_error; + + /* create homedir */ + snprintf(file, sizeof(file), "%s/efreet", efreet_cache_home_get()); + if (!ecore_file_exists(file)) + { + if (!ecore_file_mkpath(file)) goto efreet_error; + efreet_setowner(file); + } + + /* lock process, so that we only run one copy of this program */ + lockfd = cache_lock_file(); + if (lockfd == -1) goto efreet_error; + + edd = efreet_desktop_edd(); + if (!edd) goto edd_error; + + /* read user dirs from old cache */ + ef = eet_open(efreet_desktop_cache_file(), EET_FILE_MODE_READ); + if (ef) + { + user_dirs = eet_data_read(ef, efreet_array_string_edd(), EFREET_CACHE_DESKTOP_DIRS); + eet_close(ef); + } + + ef = eet_open(efreet_desktop_util_cache_file(), EET_FILE_MODE_READ); + if (ef) + { + old_file_ids = eet_data_read(ef, efreet_hash_string_edd(), "file_id"); + eet_close(ef); + } + + /* create cache */ + snprintf(file, sizeof(file), "%s.XXXXXX", efreet_desktop_cache_file()); + tmpfd = mkstemp(file); + if (tmpfd < 0) goto error; + close(tmpfd); + ef = eet_open(file, EET_FILE_MODE_READ_WRITE); + if (!ef) goto error; + + snprintf(util_file, sizeof(util_file), "%s.XXXXXX", efreet_desktop_util_cache_file()); + tmpfd = mkstemp(util_file); + if (tmpfd < 0) goto error; + close(tmpfd); + util_ef = eet_open(util_file, EET_FILE_MODE_READ_WRITE); + if (!util_ef) goto error; + + /* write cache version */ + version.major = EFREET_DESKTOP_UTILS_CACHE_MAJOR; + version.minor = EFREET_DESKTOP_UTILS_CACHE_MINOR; + eet_data_write(util_ef, efreet_version_edd(), EFREET_CACHE_VERSION, &version, 1); + version.major = EFREET_DESKTOP_CACHE_MAJOR; + version.minor = EFREET_DESKTOP_CACHE_MINOR; + eet_data_write(ef, efreet_version_edd(), EFREET_CACHE_VERSION, &version, 1); + + desktops = eina_hash_string_superfast_new(EINA_FREE_CB(efreet_desktop_free)); + + file_ids = eina_hash_string_superfast_new(NULL); + paths = eina_hash_string_superfast_new(NULL); + + mime_types = eina_hash_string_superfast_new(EINA_FREE_CB(efreet_cache_array_string_free)); + categories = eina_hash_string_superfast_new(EINA_FREE_CB(efreet_cache_array_string_free)); + startup_wm_class = eina_hash_string_superfast_new(EINA_FREE_CB(efreet_cache_array_string_free)); + name = eina_hash_string_superfast_new(EINA_FREE_CB(efreet_cache_array_string_free)); + generic_name = eina_hash_string_superfast_new(EINA_FREE_CB(efreet_cache_array_string_free)); + comment = eina_hash_string_superfast_new(EINA_FREE_CB(efreet_cache_array_string_free)); + exec = eina_hash_string_superfast_new(EINA_FREE_CB(efreet_cache_array_string_free)); + + dirs = efreet_default_dirs_get(efreet_data_home_get(), efreet_data_dirs_get(), + "applications"); + if (!dirs) goto error; + + EINA_LIST_FREE(dirs, path) + { + char file_id[PATH_MAX] = { '\0' }; + + if (!cache_scan(path, file_id, priority++, 1, &changed)) goto error; + systemdirs = eina_list_append(systemdirs, path); + } + + if (user_dirs) + { + unsigned int j; + + for (j = 0; j < user_dirs->array_count; j++) + { + if (eina_list_search_unsorted_list(systemdirs, strcmplen, user_dirs->array[j])) + continue; + if (!ecore_file_is_dir(user_dirs->array[j])) continue; + if (!cache_scan(user_dirs->array[j], NULL, priority, 0, &changed)) goto error; + + store_dirs = eina_list_append(store_dirs, user_dirs->array[j]); + } + store_dirs = eina_list_sort(store_dirs, -1, EINA_COMPARE_CB(strcmp)); + } + + if (extra_dirs) + { + Eina_List *l; + + EINA_LIST_FOREACH(extra_dirs, l, path) + { + if (eina_list_search_unsorted_list(systemdirs, strcmplen, path)) + continue; + if (eina_list_search_unsorted_list(store_dirs, EINA_COMPARE_CB(strcmp), path)) + continue; + if (!ecore_file_is_dir(path)) continue; + + /* If we scan a passed dir, we must have changed */ + changed = 1; + if (!cache_scan(path, NULL, priority, 0, &changed)) goto error; + + store_dirs = eina_list_append(store_dirs, path); + } + store_dirs = eina_list_sort(store_dirs, -1, EINA_COMPARE_CB(strcmp)); + } + + if (user_dirs) + efreet_cache_array_string_free(user_dirs); + + /* store user dirs */ + if (store_dirs) + { + Eina_List *l; + + user_dirs = NEW(Efreet_Cache_Array_String, 1); + user_dirs->array = NEW(char *, eina_list_count(store_dirs)); + user_dirs->array_count = 0; + EINA_LIST_FOREACH(store_dirs, l, path) + user_dirs->array[user_dirs->array_count++] = path; + + eet_data_write(ef, efreet_array_string_edd(), EFREET_CACHE_DESKTOP_DIRS, user_dirs, 1); + IF_FREE(user_dirs->array); + free(user_dirs); + } + + /* store util */ +#define STORE_HASH_ARRAY(_hash) \ + if (eina_hash_population((_hash)) > 0) \ + { \ + Eina_Iterator *it; \ + Efreet_Cache_Array_String array; \ + const char *str; \ + \ + hash.hash = (_hash); \ + eet_data_write(util_ef, efreet_hash_array_string_edd(), #_hash "_hash", &hash, 1); \ + array.array_count = 0; \ + array.array = malloc(eina_hash_population(hash.hash) * sizeof(char *)); \ + it = eina_hash_iterator_key_new(hash.hash); \ + EINA_ITERATOR_FOREACH(it, str) \ + array.array[array.array_count++] = str; \ + eina_iterator_free(it); \ + eet_data_write(util_ef, efreet_array_string_edd(), #_hash "_list", &array, 1); \ + free(array.array); \ + } + STORE_HASH_ARRAY(mime_types); + STORE_HASH_ARRAY(categories); + STORE_HASH_ARRAY(startup_wm_class); + STORE_HASH_ARRAY(name); + STORE_HASH_ARRAY(generic_name); + STORE_HASH_ARRAY(comment); + STORE_HASH_ARRAY(exec); + if (eina_hash_population(file_ids) > 0) + { + hash.hash = file_ids; + eet_data_write(util_ef, efreet_hash_string_edd(), "file_id", &hash, 1); + } + + eina_hash_free(mime_types); + eina_hash_free(categories); + eina_hash_free(startup_wm_class); + eina_hash_free(name); + eina_hash_free(generic_name); + eina_hash_free(comment); + eina_hash_free(exec); + + if (old_file_ids) + { + eina_hash_free(old_file_ids->hash); + free(old_file_ids); + } + + eina_hash_free(file_ids); + eina_hash_free(paths); + + eina_hash_free(desktops); + + /* check if old and new caches contain the same number of entries */ + if (!changed) + { + Eet_File *old; + + old = eet_open(efreet_desktop_cache_file(), EET_FILE_MODE_READ); + if (!old || eet_num_entries(old) != eet_num_entries(ef)) changed = 1; + if (old) eet_close(old); + old = eet_open(efreet_desktop_util_cache_file(), EET_FILE_MODE_READ); + if (!old || eet_num_entries(old) != eet_num_entries(util_ef)) changed = 1; + if (old) eet_close(old); + } + + /* cleanup */ + eet_close(util_ef); + eet_close(ef); + + /* unlink old cache files */ + if (changed) + { + if (unlink(efreet_desktop_cache_file()) < 0) + { + if (errno != ENOENT) goto error; + } + if (unlink(efreet_desktop_util_cache_file()) < 0) + { + if (errno != ENOENT) goto error; + } + /* rename tmp files to real files */ + if (rename(util_file, efreet_desktop_util_cache_file()) < 0) goto error; + efreet_setowner(efreet_desktop_util_cache_file()); + if (rename(file, efreet_desktop_cache_file()) < 0) goto error; + efreet_setowner(efreet_desktop_cache_file()); + } + else + { + unlink(util_file); + unlink(file); + } + + /* touch update file */ + snprintf(file, sizeof(file), "%s/efreet/desktop_data.update", efreet_cache_home_get()); + tmpfd = open(file, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR); + if (tmpfd >= 0) + { + char c = 'n'; + + efreet_fsetowner(tmpfd); + if (changed) c = 'c'; + if (write(tmpfd, &c, 1) != 1) perror("write"); + close(tmpfd); + } + + EINA_LIST_FREE(systemdirs, dir) + eina_stringshare_del(dir); + eina_list_free(extra_dirs); + eina_list_free(store_dirs); + efreet_shutdown(); + ecore_shutdown(); + eet_shutdown(); + eina_log_domain_unregister(_efreet_desktop_cache_log_dom); + eina_shutdown(); + close(lockfd); + return 0; +error: + IF_FREE(dir); +edd_error: + if (user_dirs) efreet_cache_array_string_free(user_dirs); + if (old_file_ids) + { + eina_hash_free(old_file_ids->hash); + free(old_file_ids); + } + efreet_shutdown(); +efreet_error: + ecore_shutdown(); +ecore_error: + eet_shutdown(); +eet_error: + EINA_LIST_FREE(systemdirs, dir) + eina_stringshare_del(dir); + eina_list_free(extra_dirs); + eina_list_free(store_dirs); + eina_log_domain_unregister(_efreet_desktop_cache_log_dom); + eina_shutdown(); +eina_error: + if (lockfd >= 0) close(lockfd); + return 1; +} diff --git a/src/bin/efreet_icon_cache_create.c b/src/bin/efreet_icon_cache_create.c new file mode 100644 index 0000000..e8d100c --- /dev/null +++ b/src/bin/efreet_icon_cache_create.c @@ -0,0 +1,1131 @@ +#ifdef HAVE_CONFIG_H +# include +#endif +#include +#include +#include +#include + +#include +#include +#include +#include + +#define EFREET_MODULE_LOG_DOM _efreet_icon_cache_log_dom +static int _efreet_icon_cache_log_dom = -1; + +#include "Efreet.h" +#include "efreet_private.h" +#include "efreet_cache_private.h" + +/* TODO: + * - Need to handle programs using different exts + */ + +static Eina_Array *exts = NULL; +static Eina_Array *extra_dirs = NULL; +static Eina_Array *strs = NULL; +static Eina_Hash *icon_themes = NULL; + +static Eina_Bool +cache_directory_modified(Eina_Hash *dirs, const char *dir) +{ + Efreet_Cache_Directory *dcache; + struct stat st; + + if (!dirs) return EINA_TRUE; + + if (stat(dir, &st) < 0) return EINA_FALSE; + dcache = eina_hash_find(dirs, dir); + if (!dcache) + { + dcache = malloc(sizeof (Efreet_Cache_Directory)); + if (!dcache) return EINA_TRUE; + + dcache->modified_time = (long long) st.st_mtime; + eina_hash_add(dirs, dir, dcache); + } + else if (dcache->modified_time == (long long) st.st_mtime) return EINA_FALSE; + dcache->modified_time = st.st_mtime; + + return EINA_TRUE; +} + +static Eina_Bool +cache_extension_lookup(const char *ext) +{ + unsigned int i; + + for (i = 0; i < exts->count; ++i) + if (!strcmp(exts->data[i], ext)) + return EINA_TRUE; + return EINA_FALSE; +} + +static Eina_Bool +cache_fallback_scan_dir(Eina_Hash *icons, Eina_Hash *dirs, const char *dir) +{ + Eina_Iterator *it; + Eina_File_Direct_Info *entry; + + if (!cache_directory_modified(dirs, dir)) return EINA_TRUE; + + it = eina_file_stat_ls(dir); + if (!it) return EINA_TRUE; + + EINA_ITERATOR_FOREACH(it, entry) + { + Efreet_Cache_Fallback_Icon *icon; + char *name; + char *ext; + unsigned int i; + + if (entry->type == EINA_FILE_DIR) + continue; + + ext = strrchr(entry->path + entry->name_start, '.'); + if (!ext || !cache_extension_lookup(ext)) + continue; + + /* icon with known extension */ + name = entry->path + entry->name_start; + *ext = '\0'; + + icon = eina_hash_find(icons, name); + if (!icon) + { + icon = NEW(Efreet_Cache_Fallback_Icon, 1); + icon->theme = NULL; + eina_hash_add(icons, name, icon); + } + + *ext = '.'; + + for (i = 0; i < icon->icons_count; ++i) + if (!strcmp(icon->icons[i], entry->path)) + break; + + if (i != icon->icons_count) + continue; + + icon->icons = realloc(icon->icons, sizeof (char *) * (icon->icons_count + 1)); + icon->icons[icon->icons_count] = eina_stringshare_add(entry->path); + eina_array_push(strs, icon->icons[icon->icons_count++]); + } + + eina_iterator_free(it); + + return EINA_TRUE; +} + +static Eina_Bool +cache_fallback_scan(Eina_Hash *icons, Eina_Hash *dirs) +{ + unsigned int i; + Eina_List *xdg_dirs, *l; + const char *dir; + char path[PATH_MAX]; + + for (i = 0; i < extra_dirs->count; i++) + cache_fallback_scan_dir(icons, dirs, extra_dirs->data[i]); + + cache_fallback_scan_dir(icons, dirs, efreet_icon_deprecated_user_dir_get()); + cache_fallback_scan_dir(icons, dirs, efreet_icon_user_dir_get()); + + xdg_dirs = efreet_data_dirs_get(); + EINA_LIST_FOREACH(xdg_dirs, l, dir) + { + snprintf(path, sizeof(path), "%s/icons", dir); + cache_fallback_scan_dir(icons, dirs, path); + } + +#ifndef STRICT_SPEC + EINA_LIST_FOREACH(xdg_dirs, l, dir) + { + snprintf(path, sizeof(path), "%s/pixmaps", dir); + cache_fallback_scan_dir(icons, dirs, path); + } +#endif + + cache_fallback_scan_dir(icons, dirs, "/usr/share/pixmaps"); + + return EINA_TRUE; +} + +static Eina_Bool +check_fallback_changed(Efreet_Cache_Icon_Theme *theme) +{ + unsigned int i; + Eina_List *xdg_dirs, *l; + const char *dir; + char path[PATH_MAX]; + + /* Check if the dirs we have cached are changed */ + if (theme->dirs) + { + Eina_Iterator *it; + Eina_Bool changed = EINA_FALSE; + + it = eina_hash_iterator_key_new(theme->dirs); + EINA_ITERATOR_FOREACH(it, dir) + { + changed = !ecore_file_exists(dir); + if (changed) break; + changed = cache_directory_modified(theme->dirs, dir); + if (changed) break; + } + eina_iterator_free(it); + if (changed) return EINA_TRUE; + } + + /* Check if spec dirs have changed */ + for (i = 0; i < extra_dirs->count; i++) + if (cache_directory_modified(theme->dirs, extra_dirs->data[i])) return EINA_TRUE; + + if (cache_directory_modified(theme->dirs, efreet_icon_deprecated_user_dir_get())) return EINA_TRUE; + if (cache_directory_modified(theme->dirs, efreet_icon_user_dir_get())) return EINA_TRUE; + + xdg_dirs = efreet_data_dirs_get(); + EINA_LIST_FOREACH(xdg_dirs, l, dir) + { + snprintf(path, sizeof(path), "%s/icons", dir); + if (cache_directory_modified(theme->dirs, path)) return EINA_TRUE; + } + +#ifndef STRICT_SPEC + EINA_LIST_FOREACH(xdg_dirs, l, dir) + { + snprintf(path, sizeof(path), "%s/pixmaps", dir); + if (cache_directory_modified(theme->dirs, path)) return EINA_TRUE; + } +#endif + + if (cache_directory_modified(theme->dirs, "/usr/share/pixmaps")) return EINA_TRUE; + return EINA_FALSE; +} + +static Eina_Bool +cache_scan_path_dir(Efreet_Icon_Theme *theme, + const char *path, + Efreet_Icon_Theme_Directory *dir, + Eina_Hash *icons) +{ + Eina_Iterator *it; + char buf[PATH_MAX]; + Eina_File_Direct_Info *entry; + + snprintf(buf, sizeof(buf), "%s/%s", path, dir->name); + + it = eina_file_stat_ls(buf); + if (!it) return EINA_TRUE; + + EINA_ITERATOR_FOREACH(it, entry) + { + Efreet_Cache_Icon *icon; + char *name; + char *ext; + unsigned int i; + + if (entry->type == EINA_FILE_DIR) + continue; + + ext = strrchr(entry->path + entry->name_start, '.'); + if (!ext || !cache_extension_lookup(ext)) + continue; + + /* icon with known extension */ + name = entry->path + entry->name_start; + *ext = '\0'; + + icon = eina_hash_find(icons, name); + if (!icon) + { + icon = NEW(Efreet_Cache_Icon, 1); + icon->theme = eina_stringshare_add(theme->name.internal); + eina_array_push(strs, icon->theme); + eina_hash_add(icons, name, icon); + } + else if (icon->theme && strcmp(icon->theme, theme->name.internal)) + { + /* We got this icon from a parent theme */ + continue; + } + + /* find if we have the same icon in another type */ + for (i = 0; i < icon->icons_count; ++i) + { + if ((icon->icons[i]->type == dir->type) && + (icon->icons[i]->normal == dir->size.normal) && + (icon->icons[i]->max == dir->size.max) && + (icon->icons[i]->min == dir->size.min)) + break; + } + + *ext = '.'; + + if (i != icon->icons_count) + { + unsigned int j; + + /* check if the path already exist */ + for (j = 0; j < icon->icons[i]->paths_count; ++j) + if (!strcmp(icon->icons[i]->paths[j], entry->path)) + break; + + if (j != icon->icons[i]->paths_count) + continue; + } + /* no icon match so add a new one */ + else + { + icon->icons = realloc(icon->icons, + sizeof (Efreet_Cache_Icon_Element*) * (++icon->icons_count)); + icon->icons[i] = NEW(Efreet_Cache_Icon_Element, 1); + icon->icons[i]->type = dir->type; + icon->icons[i]->normal = dir->size.normal; + icon->icons[i]->min = dir->size.min; + icon->icons[i]->max = dir->size.max; + icon->icons[i]->paths = NULL; + icon->icons[i]->paths_count = 0; + } + + /* and finally store the path */ + icon->icons[i]->paths = realloc(icon->icons[i]->paths, + sizeof (char*) * (icon->icons[i]->paths_count + 1)); + icon->icons[i]->paths[icon->icons[i]->paths_count] = eina_stringshare_add(entry->path); + eina_array_push(strs, icon->icons[i]->paths[icon->icons[i]->paths_count++]); + } + + eina_iterator_free(it); + + return EINA_TRUE; +} + +static Eina_Bool +cache_scan_path(Efreet_Icon_Theme *theme, Eina_Hash *icons, const char *path) +{ + Eina_List *l; + Efreet_Icon_Theme_Directory *dir; + + EINA_LIST_FOREACH(theme->directories, l, dir) + if (!cache_scan_path_dir(theme, path, dir, icons)) return EINA_FALSE; + + return EINA_TRUE; +} + +static Eina_Bool +cache_scan(Efreet_Icon_Theme *theme, Eina_Hash *themes, Eina_Hash *icons) +{ + Eina_List *l; + const char *path; + const char *name; + + if (!theme) return EINA_TRUE; + if (eina_hash_find(themes, theme->name.internal)) return EINA_TRUE; + eina_hash_direct_add(themes, theme->name.internal, theme); + + /* scan theme */ + EINA_LIST_FOREACH(theme->paths, l, path) + if (!cache_scan_path(theme, icons, path)) return EINA_FALSE; + + /* scan inherits */ + if (theme->inherits) + { + EINA_LIST_FOREACH(theme->inherits, l, name) + { + Efreet_Icon_Theme *inherit; + + inherit = eina_hash_find(icon_themes, name); + if (!inherit) + INF("Theme `%s` not found for `%s`.", + name, theme->name.internal); + if (!cache_scan(inherit, themes, icons)) return EINA_FALSE; + } + } + else if (strcmp(theme->name.internal, "hicolor")) + { + theme = eina_hash_find(icon_themes, "hicolor"); + if (!cache_scan(theme, themes, icons)) return EINA_FALSE; + } + + return EINA_TRUE; +} + +static Eina_Bool +check_changed(Efreet_Cache_Icon_Theme *theme) +{ + Eina_List *l; + const char *name; + + if (!theme) return EINA_FALSE; + + if (theme->changed) return EINA_TRUE; + if (theme->theme.inherits) + { + EINA_LIST_FOREACH(theme->theme.inherits, l, name) + { + Efreet_Cache_Icon_Theme *inherit; + + inherit = eina_hash_find(icon_themes, name); + if (!inherit) + INF("Theme `%s` not found for `%s`.", + name, theme->theme.name.internal); + if (check_changed(inherit)) return EINA_TRUE; + } + } + else if (strcmp(theme->theme.name.internal, "hicolor")) + { + theme = eina_hash_find(icon_themes, "hicolor"); + if (check_changed(theme)) return EINA_TRUE; + } + return EINA_FALSE; +} + +static Efreet_Icon_Theme_Directory * +icon_theme_directory_new(Efreet_Ini *ini, const char *name) +{ + Efreet_Icon_Theme_Directory *dir; + int val; + const char *tmp; + + if (!ini) return NULL; + + dir = NEW(Efreet_Icon_Theme_Directory, 1); + if (!dir) return NULL; + dir->name = eina_stringshare_add(name); + eina_array_push(strs, dir->name); + + efreet_ini_section_set(ini, name); + + tmp = efreet_ini_string_get(ini, "Context"); + if (tmp) + { + if (!strcasecmp(tmp, "Actions")) + dir->context = EFREET_ICON_THEME_CONTEXT_ACTIONS; + + else if (!strcasecmp(tmp, "Devices")) + dir->context = EFREET_ICON_THEME_CONTEXT_DEVICES; + + else if (!strcasecmp(tmp, "FileSystems")) + dir->context = EFREET_ICON_THEME_CONTEXT_FILESYSTEMS; + + else if (!strcasecmp(tmp, "MimeTypes")) + dir->context = EFREET_ICON_THEME_CONTEXT_MIMETYPES; + } + + /* Threshold is fallback */ + dir->type = EFREET_ICON_SIZE_TYPE_THRESHOLD; + + tmp = efreet_ini_string_get(ini, "Type"); + if (tmp) + { + if (!strcasecmp(tmp, "Fixed")) + dir->type = EFREET_ICON_SIZE_TYPE_FIXED; + + else if (!strcasecmp(tmp, "Scalable")) + dir->type = EFREET_ICON_SIZE_TYPE_SCALABLE; + } + + dir->size.normal = efreet_ini_int_get(ini, "Size"); + + if (dir->type == EFREET_ICON_SIZE_TYPE_THRESHOLD) + { + val = efreet_ini_int_get(ini, "Threshold"); + if (val < 0) val = 2; + dir->size.max = dir->size.normal + val; + dir->size.min = dir->size.normal - val; + } + else if (dir->type == EFREET_ICON_SIZE_TYPE_SCALABLE) + { + val = efreet_ini_int_get(ini, "MinSize"); + if (val < 0) dir->size.min = dir->size.normal; + else dir->size.min = val; + + val = efreet_ini_int_get(ini, "MaxSize"); + if (val < 0) dir->size.max = dir->size.normal; + else dir->size.max = val; + } + + return dir; +} + +static Eina_Bool +icon_theme_index_read(Efreet_Cache_Icon_Theme *theme, const char *path) +{ + Efreet_Ini *ini; + Efreet_Icon_Theme_Directory *dir; + const char *tmp; + struct stat st; + char rp[PATH_MAX]; + + if (!theme || !path) return EINA_FALSE; + + if (!realpath(path, rp)) return EINA_FALSE; + + if (stat(rp, &st) < 0) return EINA_FALSE; + if (theme->path && !strcmp(theme->path, rp) && theme->last_cache_check >= (long long) st.st_mtime) + { + /* no change */ + theme->valid = 1; + return EINA_TRUE; + } + if (!theme->path || strcmp(theme->path, rp)) + { + theme->path = eina_stringshare_add(rp); + eina_array_push(strs, theme->path); + } + if ((long long) st.st_mtime > theme->last_cache_check) + theme->last_cache_check = (long long) st.st_mtime; + theme->changed = 1; + + ini = efreet_ini_new(path); + if (!ini) return EINA_FALSE; + if (!ini->data) + { + efreet_ini_free(ini); + return EINA_FALSE; + } + + efreet_ini_section_set(ini, "Icon Theme"); + tmp = efreet_ini_localestring_get(ini, "Name"); + if (tmp) + { + theme->theme.name.name = eina_stringshare_add(tmp); + eina_array_push(strs, theme->theme.name.name); + } + + tmp = efreet_ini_localestring_get(ini, "Comment"); + if (tmp) + { + theme->theme.comment = eina_stringshare_add(tmp); + eina_array_push(strs, theme->theme.comment); + } + + tmp = efreet_ini_string_get(ini, "Example"); + if (tmp) + { + theme->theme.example_icon = eina_stringshare_add(tmp); + eina_array_push(strs, theme->theme.example_icon); + } + + theme->hidden = efreet_ini_boolean_get(ini, "Hidden"); + + theme->valid = 1; + + /* Check the inheritance. If there is none we inherit from the hicolor theme */ + tmp = efreet_ini_string_get(ini, "Inherits"); + if (tmp) + { + char *t, *s, *p; + const char *i; + size_t len; + + len = strlen(tmp) + 1; + t = alloca(len); + memcpy(t, tmp, len); + s = t; + p = strchr(s, ','); + + while (p) + { + *p = '\0'; + + i = eina_stringshare_add(s); + theme->theme.inherits = eina_list_append(theme->theme.inherits, i); + eina_array_push(strs, i); + s = ++p; + p = strchr(s, ','); + } + i = eina_stringshare_add(s); + theme->theme.inherits = eina_list_append(theme->theme.inherits, i); + eina_array_push(strs, i); + } + + /* make sure this one is done last as setting the directory will change + * the ini section ... */ + tmp = efreet_ini_string_get(ini, "Directories"); + if (tmp) + { + char *t, *s, *p; + size_t len; + + len = strlen(tmp) + 1; + t = alloca(len); + memcpy(t, tmp, len); + s = t; + p = s; + + while (p) + { + p = strchr(s, ','); + + if (p) *p = '\0'; + + dir = icon_theme_directory_new(ini, s); + if (!dir) goto error; + theme->theme.directories = eina_list_append(theme->theme.directories, dir); + + if (p) s = ++p; + } + } + +error: + efreet_ini_free(ini); + + return EINA_TRUE; +} + +static Eina_Bool +cache_theme_scan(const char *dir) +{ + Eina_Iterator *it; + Eina_File_Direct_Info *entry; + + it = eina_file_stat_ls(dir); + if (!it) return EINA_TRUE; + + EINA_ITERATOR_FOREACH(it, entry) + { + Efreet_Cache_Icon_Theme *theme; + const char *name; + const char *path; + char buf[PATH_MAX]; + struct stat st; + + if (stat(entry->path, &st) < 0) continue; + + if ((entry->type != EINA_FILE_DIR) && + (entry->type != EINA_FILE_LNK)) + continue; + + name = entry->path + entry->name_start; + theme = eina_hash_find(icon_themes, name); + + if (!theme) + { + theme = NEW(Efreet_Cache_Icon_Theme, 1); + theme->theme.name.internal = eina_stringshare_add(name); + eina_array_push(strs, theme->theme.name.internal); + eina_hash_direct_add(icon_themes, + (void *)theme->theme.name.internal, theme); + theme->changed = 1; + } + if ((long long) st.st_mtime > theme->last_cache_check) + { + theme->last_cache_check = (long long) st.st_mtime; + theme->changed = 1; + } + + /* TODO: We need to handle change in order of included paths */ + if (!eina_list_search_unsorted(theme->theme.paths, EINA_COMPARE_CB(strcmp), entry->path)) + { + path = eina_stringshare_add(entry->path); + theme->theme.paths = eina_list_append(theme->theme.paths, path); + eina_array_push(strs, path); + theme->changed = 1; + } + + /* we're already valid so no reason to check for an index.theme file */ + if (theme->valid) continue; + + /* if the index.theme file exists we parse it into the theme */ + memcpy(buf, entry->path, entry->path_length); + memcpy(buf + entry->path_length, "/index.theme", sizeof("/index.theme")); + if (ecore_file_exists(buf)) + { + if (!icon_theme_index_read(theme, buf)) + theme->valid = 0; + } + } + eina_iterator_free(it); + return EINA_TRUE; +} + +static int +cache_lock_file(void) +{ + char file[PATH_MAX]; + struct flock fl; + int lockfd; + + snprintf(file, sizeof(file), "%s/efreet/icon_data.lock", efreet_cache_home_get()); + lockfd = open(file, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); + if (lockfd < 0) return -1; + efreet_fsetowner(lockfd); + + memset(&fl, 0, sizeof(struct flock)); + fl.l_type = F_WRLCK; + fl.l_whence = SEEK_SET; + if (fcntl(lockfd, F_SETLK, &fl) < 0) + { + WRN("LOCKED! You may want to delete %s if this persists", file); + close(lockfd); + return -1; + } + + return lockfd; +} + +static void +icon_theme_free(Efreet_Cache_Icon_Theme *theme) +{ + void *data; + + eina_list_free(theme->theme.paths); + eina_list_free(theme->theme.inherits); + EINA_LIST_FREE(theme->theme.directories, data) + free(data); + if (theme->dirs) efreet_hash_free(theme->dirs, free); + free(theme); +} + +/** + * @internal + * @return EINA_TRUE if data adds new + */ +static Eina_Bool +add_data(Eet_File *ef, Eina_Array *data, const char *key) +{ + Efreet_Cache_Array_String *add; + unsigned int i, j; + Eina_Bool added = EINA_FALSE; + + add = eet_data_read(ef, efreet_array_string_edd(), key); + if (!add) return EINA_TRUE; + /* loop once to check added */ + for (i = 0; i < data->count; i++) + { + int found = 0; + for (j = 0; j < add->array_count; ++j) + { + if (!strcmp(add->array[j], data->data[i])) + { + found = 1; + break; + } + } + if (!found) + { + added = EINA_TRUE; + break; + } + } + /* loop again to add all data */ + for (i = 0; i < add->array_count; i++) + { + int found = 0; + for (j = 0; j < data->count; ++j) + { + if (!strcmp(add->array[i], data->data[j])) + { + found = 1; + break; + } + } + if (!found) + eina_array_push(data, add->array[i]); + } + IF_FREE(add->array); + free(add); + + return added; +} + + +static void +save_data(Eet_File *ef, Eina_Array *data, const char *key) +{ + Efreet_Cache_Array_String *save; + unsigned int i; + + if (!data || !data->count) return; + + save = NEW(Efreet_Cache_Array_String, 1); + save->array = NEW(char *, data->count); + save->array_count = 0; + for (i = 0; i < data->count; ++i) + save->array[save->array_count++] = data->data[i]; + eet_data_write(ef, efreet_array_string_edd(), key, save, 1); + IF_FREE(save->array); + free(save); +} + +int +main(int argc, char **argv) +{ + /* TODO: + * - Add file monitor on files, so that we catch changes on files + * during whilst this program runs. + * - Maybe linger for a while to reduce number of cache re-creates. + */ + Eina_Iterator *it; + Efreet_Cache_Version *icon_version; + Efreet_Cache_Version *theme_version; + Efreet_Cache_Icon_Theme *theme; + Eet_Data_Descriptor *theme_edd; + Eet_Data_Descriptor *icon_edd; + Eet_Data_Descriptor *fallback_edd; + Eet_File *icon_ef; + Eet_File *theme_ef; + Eina_List *xdg_dirs = NULL; + Eina_List *l = NULL; + char file[PATH_MAX]; + const char *path; + char *dir = NULL; + Eina_Bool changed = EINA_FALSE; + Eina_Bool flush = EINA_FALSE; + int lockfd = -1; + int tmpfd = -1; + char **keys; + int num, i; + + /* init external subsystems */ + if (!eina_init()) return -1; + _efreet_icon_cache_log_dom = + eina_log_domain_register("efreet_icon_cache", EFREET_DEFAULT_LOG_COLOR); + if (_efreet_icon_cache_log_dom < 0) + { + EINA_LOG_ERR("Efreet: Could not create a log domain for efreet_icon_cache."); + return -1; + } + + eina_log_domain_level_set("efreet_icon_cache", EINA_LOG_LEVEL_ERR); + + exts = eina_array_new(10); + extra_dirs = eina_array_new(10); + + for (i = 1; i < argc; i++) + { + if (!strcmp(argv[i], "-v")) + eina_log_domain_level_set("efreet_icon_cache", EINA_LOG_LEVEL_DBG); + else if ((!strcmp(argv[i], "-h")) || + (!strcmp(argv[i], "-help")) || + (!strcmp(argv[i], "--h")) || + (!strcmp(argv[i], "--help"))) + { + printf("Options:\n"); + printf(" -v Verbose mode\n"); + printf(" -e .ext1 .ext2 Extensions\n"); + printf(" -d dir1 dir2 Extra dirs\n"); + exit(0); + } + else if (!strcmp(argv[i], "-e")) + { + while ((i < (argc - 1)) && (argv[(i + 1)][0] != '-')) + eina_array_push(exts, argv[++i]); + } + else if (!strcmp(argv[i], "-d")) + { + while ((i < (argc - 1)) && (argv[(i + 1)][0] != '-')) + eina_array_push(extra_dirs, argv[++i]); + } + } + + if (!eet_init()) return -1; + if (!ecore_init()) return -1; + + efreet_cache_update = 0; + /* finish efreet init */ + if (!efreet_init()) goto on_error; + + strs = eina_array_new(32); + + /* create homedir */ + snprintf(file, sizeof(file), "%s/efreet", efreet_cache_home_get()); + if (!ecore_file_exists(file)) + { + if (!ecore_file_mkpath(file)) return -1; + efreet_setowner(file); + } + + /* lock process, so that we only run one copy of this program */ + lockfd = cache_lock_file(); + if (lockfd == -1) goto on_error; + + /* Need to init edd's, so they are like we want, not like userspace wants */ + icon_edd = efreet_icon_edd(); + fallback_edd = efreet_icon_fallback_edd(); + theme_edd = efreet_icon_theme_edd(EINA_TRUE); + + icon_themes = eina_hash_string_superfast_new(EINA_FREE_CB(icon_theme_free)); + + INF("opening theme cache"); + /* open theme file */ + theme_ef = eet_open(efreet_icon_theme_cache_file(), EET_FILE_MODE_READ_WRITE); + if (!theme_ef) goto on_error_efreet; + theme_version = eet_data_read(theme_ef, efreet_version_edd(), EFREET_CACHE_VERSION); + if (theme_version && + ((theme_version->major != EFREET_ICON_CACHE_MAJOR) || + (theme_version->minor != EFREET_ICON_CACHE_MINOR))) + { + // delete old cache + eet_close(theme_ef); + if (unlink(efreet_icon_theme_cache_file()) < 0) + { + if (errno != ENOENT) goto on_error_efreet; + } + theme_ef = eet_open(efreet_icon_theme_cache_file(), EET_FILE_MODE_READ_WRITE); + if (!theme_ef) goto on_error_efreet; + } + if (!theme_version) + theme_version = NEW(Efreet_Cache_Version, 1); + + theme_version->major = EFREET_ICON_CACHE_MAJOR; + theme_version->minor = EFREET_ICON_CACHE_MINOR; + + if (add_data(theme_ef, exts, EFREET_CACHE_ICON_EXTENSIONS)) + flush = EINA_TRUE; + if (add_data(theme_ef, extra_dirs, EFREET_CACHE_ICON_EXTRA_DIRS)) + flush = EINA_TRUE; + if (flush) + changed = EINA_TRUE; + + if (exts->count == 0) + { + ERR("Need to pass extensions to icon cache create process"); + goto on_error_efreet; + } + + keys = eet_list(theme_ef, "*", &num); + if (keys) + { + for (i = 0; i < num; i++) + { + if (!strncmp(keys[i], "__efreet", 8)) continue; + theme = eet_data_read(theme_ef, theme_edd, keys[i]); + if (theme) + { + theme->valid = 0; + eina_hash_direct_add(icon_themes, theme->theme.name.internal, theme); + } + } + free(keys); + } + + INF("scan for themes"); + /* scan themes */ + cache_theme_scan(efreet_icon_deprecated_user_dir_get()); + cache_theme_scan(efreet_icon_user_dir_get()); + + xdg_dirs = efreet_data_dirs_get(); + EINA_LIST_FOREACH(xdg_dirs, l, dir) + { + snprintf(file, sizeof(file), "%s/icons", dir); + cache_theme_scan(file); + } + +#ifndef STRICT_SPEC + EINA_LIST_FOREACH(xdg_dirs, l, dir) + { + snprintf(file, sizeof(file), "%s/pixmaps", dir); + cache_theme_scan(file); + } +#endif + + cache_theme_scan("/usr/share/pixmaps"); + + /* scan icons */ + it = eina_hash_iterator_data_new(icon_themes); + EINA_ITERATOR_FOREACH(it, theme) + { + if (!theme->valid) continue; +#ifndef STRICT_SPEC + if (!theme->theme.name.name) continue; +#endif + INF("scan theme %s", theme->theme.name.name); + + theme->changed = check_changed(theme); + if (flush) + theme->changed = EINA_TRUE; + + INF("open icon file"); + /* open icon file */ + icon_ef = eet_open(efreet_icon_cache_file(theme->theme.name.internal), EET_FILE_MODE_READ_WRITE); + if (!icon_ef) goto on_error_efreet; + icon_version = eet_data_read(icon_ef, efreet_version_edd(), EFREET_CACHE_VERSION); + if (theme->changed || (icon_version && + ((icon_version->major != EFREET_ICON_CACHE_MAJOR) || + (icon_version->minor != EFREET_ICON_CACHE_MINOR)))) + { + // delete old cache + eet_close(icon_ef); + if (unlink(efreet_icon_cache_file(theme->theme.name.internal)) < 0) + { + if (errno != ENOENT) goto on_error_efreet; + } + icon_ef = eet_open(efreet_icon_cache_file(theme->theme.name.internal), EET_FILE_MODE_READ_WRITE); + if (!icon_ef) goto on_error_efreet; + theme->changed = EINA_TRUE; + } + + if (theme->changed) + changed = EINA_TRUE; + + if (theme->changed) + { + Eina_Hash *themes; + Eina_Hash *icons; + + if (!icon_version) + icon_version = NEW(Efreet_Cache_Version, 1); + + icon_version->major = EFREET_ICON_CACHE_MAJOR; + icon_version->minor = EFREET_ICON_CACHE_MINOR; + + themes = eina_hash_string_superfast_new(NULL); + icons = eina_hash_string_superfast_new(NULL); + + INF("scan icons\n"); + if (cache_scan(&(theme->theme), themes, icons)) + { + Eina_Iterator *icons_it; + Eina_Hash_Tuple *tuple; + + INF("generated: '%s' %i (%i)", + theme->theme.name.internal, + changed, + eina_hash_population(icons)); + + icons_it = eina_hash_iterator_tuple_new(icons); + EINA_ITERATOR_FOREACH(icons_it, tuple) + eet_data_write(icon_ef, icon_edd, tuple->key, tuple->data, 1); + eina_iterator_free(icons_it); + + INF("theme change: %s %lld", theme->theme.name.internal, theme->last_cache_check); + eet_data_write(theme_ef, theme_edd, theme->theme.name.internal, theme, 1); + } + eina_hash_free(themes); + eina_hash_free(icons); + } + + eet_data_write(icon_ef, efreet_version_edd(), EFREET_CACHE_VERSION, icon_version, 1); + eet_close(icon_ef); + efreet_setowner(efreet_icon_cache_file(theme->theme.name.internal)); + free(icon_version); + } + eina_iterator_free(it); + + INF("scan fallback icons"); + theme = eet_data_read(theme_ef, theme_edd, EFREET_CACHE_ICON_FALLBACK); + if (!theme) + { + theme = NEW(Efreet_Cache_Icon_Theme, 1); + theme->changed = EINA_TRUE; + } + if (flush) + theme->changed = EINA_TRUE; + + INF("open fallback file"); + /* open icon file */ + icon_ef = eet_open(efreet_icon_cache_file(EFREET_CACHE_ICON_FALLBACK), EET_FILE_MODE_READ_WRITE); + if (!icon_ef) goto on_error_efreet; + icon_version = eet_data_read(icon_ef, efreet_version_edd(), EFREET_CACHE_VERSION); + if (theme->changed || (icon_version && + ((icon_version->major != EFREET_ICON_CACHE_MAJOR) || + (icon_version->minor != EFREET_ICON_CACHE_MINOR)))) + { + // delete old cache + eet_close(icon_ef); + if (unlink(efreet_icon_cache_file(EFREET_CACHE_ICON_FALLBACK)) < 0) + { + if (errno != ENOENT) goto on_error_efreet; + } + icon_ef = eet_open(efreet_icon_cache_file(EFREET_CACHE_ICON_FALLBACK), EET_FILE_MODE_READ_WRITE); + if (!icon_ef) goto on_error_efreet; + theme->changed = EINA_TRUE; + } + if (!theme->changed) + theme->changed = check_fallback_changed(theme); + if (theme->changed && theme->dirs) + { + efreet_hash_free(theme->dirs, free); + theme->dirs = NULL; + } + if (!theme->dirs) + theme->dirs = eina_hash_string_superfast_new(NULL); + + if (theme->changed) + changed = EINA_TRUE; + + if (theme->changed) + { + Eina_Hash *icons; + + if (!icon_version) + icon_version = NEW(Efreet_Cache_Version, 1); + + icon_version->major = EFREET_ICON_CACHE_MAJOR; + icon_version->minor = EFREET_ICON_CACHE_MINOR; + + icons = eina_hash_string_superfast_new(NULL); + + INF("scan fallback icons"); + /* Save fallback in the right part */ + if (cache_fallback_scan(icons, theme->dirs)) + { + Eina_Iterator *icons_it; + Eina_Hash_Tuple *tuple; + + INF("generated: fallback %i (%i)", theme->changed, eina_hash_population(icons)); + + icons_it = eina_hash_iterator_tuple_new(icons); + EINA_ITERATOR_FOREACH(icons_it, tuple) + eet_data_write(icon_ef, fallback_edd, tuple->key, tuple->data, 1); + eina_iterator_free(icons_it); + } + eina_hash_free(icons); + + eet_data_write(theme_ef, theme_edd, EFREET_CACHE_ICON_FALLBACK, theme, 1); + } + + icon_theme_free(theme); + + eet_data_write(icon_ef, efreet_version_edd(), EFREET_CACHE_VERSION, icon_version, 1); + eet_close(icon_ef); + efreet_setowner(efreet_icon_cache_file(EFREET_CACHE_ICON_FALLBACK)); + free(icon_version); + + eina_hash_free(icon_themes); + + /* save data */ + eet_data_write(theme_ef, efreet_version_edd(), EFREET_CACHE_VERSION, theme_version, 1); + save_data(theme_ef, exts, EFREET_CACHE_ICON_EXTENSIONS); + save_data(theme_ef, extra_dirs, EFREET_CACHE_ICON_EXTRA_DIRS); + + eet_close(theme_ef); + efreet_setowner(efreet_icon_theme_cache_file()); + free(theme_version); + + /* touch update file */ + snprintf(file, sizeof(file), "%s/efreet/icon_data.update", efreet_cache_home_get()); + tmpfd = open(file, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR); + if (tmpfd >= 0) + { + char c = 'n'; + + efreet_fsetowner(tmpfd); + if (changed) c = 'c'; + if (write(tmpfd, &c, 1) != 1) perror("write"); + close(tmpfd); + } + + INF("done"); +on_error_efreet: + efreet_shutdown(); + +on_error: + if (lockfd >= 0) close(lockfd); + + while ((path = eina_array_pop(strs))) + eina_stringshare_del(path); + eina_array_free(strs); + eina_array_free(exts); + eina_array_free(extra_dirs); + + ecore_shutdown(); + eet_shutdown(); + eina_log_domain_unregister(_efreet_icon_cache_log_dom); + eina_shutdown(); + + return 0; +} diff --git a/src/lib/Efreet.h b/src/lib/Efreet.h new file mode 100644 index 0000000..c5b8104 --- /dev/null +++ b/src/lib/Efreet.h @@ -0,0 +1,107 @@ +#ifndef EFREET_H +#define EFREET_H + +/** + * @file Efreet.h + * @brief The file that must be included by any project wishing to use + * Efreet. Efreet.h provides all of the necessary headers and includes to + * work with Efreet. + */ + +/** + * @mainpage The Efreet Library + * + * @section intro Introduction + * + * Efreet is a library designed to help apps work several of the + * Freedesktop.org standards regarding Icons, Desktop files and Menus. To + * that end it implements the following specifications: + * + * @li XDG Base Directory Specification + * @li Icon Theme Specification + * @li Desktop Entry Specification + * @li Desktop Menu Specification + * @li FDO URI Specification + * @li Shared Mime Info Specification + * @li Trash Specification + */ + +#include + +#ifdef EAPI +# undef EAPI +#endif + +#ifdef _WIN32 +# ifdef EFL_EFREET_BUILD +# ifdef DLL_EXPORT +# define EAPI __declspec(dllexport) +# else +# define EAPI +# endif /* ! DLL_EXPORT */ +# else +# define EAPI __declspec(dllimport) +# endif /* ! EFL_EFREET_BUILD */ +#else +# ifdef __GNUC__ +# if __GNUC__ >= 4 +# define EAPI __attribute__ ((visibility("default"))) +# else +# define EAPI +# endif +# else +# define EAPI +# endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define EFREET_VERSION_MAJOR 1 +#define EFREET_VERSION_MINOR 7 + + typedef struct _Efreet_Version + { + int major; + int minor; + int micro; + int revision; + } Efreet_Version; + + EAPI extern Efreet_Version *efreet_version; + +#include "efreet_base.h" +#include "efreet_ini.h" +#include "efreet_icon.h" +#include "efreet_desktop.h" +#include "efreet_menu.h" +#include "efreet_utils.h" +#include "efreet_uri.h" + +/** + * @return Value > @c 0 if the initialization was successful, @c 0 otherwise. + * @brief Initializes the Efreet system + */ +EAPI int efreet_init(void); + +/** + * @return The number of times the init function has been called minus the + * corresponding init call. + * @brief Shuts down Efreet if a balanced number of init/shutdown calls have + * been made + */ +EAPI int efreet_shutdown(void); + +/** + * @brief Resets language dependent variables and resets language dependent + * caches This must be called whenever the locale is changed. + * @since 1.7 + */ +EAPI void efreet_lang_reset(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/lib/Efreet_Mime.h b/src/lib/Efreet_Mime.h new file mode 100644 index 0000000..d6873fc --- /dev/null +++ b/src/lib/Efreet_Mime.h @@ -0,0 +1,125 @@ +#ifndef EFREET_MIME_H +#define EFREET_MIME_H + +/** + * @file Efreet_Mime.h + * @brief The file that must be included by any project wishing to use + * @addtogroup Efreet_Mime Efreet_Mime: The XDG Shared Mime Info standard + * Efreet Mime is a library designed to help apps work with the + * Freedesktop.org Shared Mime Info standard. + * Efreet_Mime.h provides all of the necessary headers and + * includes to work with Efreet_Mime. + * @{ + */ + +#ifdef EAPI +# undef EAPI +#endif + +#ifdef _WIN32 +# ifdef EFL_EFREET_MIME_BUILD +# ifdef DLL_EXPORT +# define EAPI __declspec(dllexport) +# else +# define EAPI +# endif /* ! DLL_EXPORT */ +# else +# define EAPI __declspec(dllimport) +# endif /* ! EFL_EFREET_MIME_BUILD */ +#else +# ifdef __GNUC__ +# if __GNUC__ >= 4 +# define EAPI __attribute__ ((visibility("default"))) +# else +# define EAPI +# endif +# else +# define EAPI +# endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * @return @c 1 on success or @c 0 on failure. + * @brief Initializes the efreet mime settings + */ +EAPI int efreet_mime_init(void); + +/** + * @return No value. + * @brief Cleans up the efreet mime settings system + */ +EAPI int efreet_mime_shutdown(void); + +/** + * @param file The file to find the mime type + * @return Mime type as a string. + * @brief Retrieve the mime type of a file + */ +EAPI const char *efreet_mime_type_get(const char *file); + +/** + * @param file The file to check the mime type + * @return Mime type as a string. + * @brief Retrieve the mime type of a file using magic + */ +EAPI const char *efreet_mime_magic_type_get(const char *file); + +/** + * @param file The file to check the mime type + * @return Mime type as a string. + * @brief Retrieve the mime type of a file using globs + */ +EAPI const char *efreet_mime_globs_type_get(const char *file); + +/** + * @param file The file to check the mime type + * @return Mime type as a string. + * @brief Retrieve the special mime type of a file + */ +EAPI const char *efreet_mime_special_type_get(const char *file); + +/** + * @param file The file to check the mime type + * @return Mime type as a string. + * @brief Retrieve the fallback mime type of a file. + */ +EAPI const char *efreet_mime_fallback_type_get(const char *file); + + +/** + * @param mime The name of the mime type + * @param theme The name of the theme to search icons in + * @param size The wanted size of the icon + * @return Mime type icon path as a string. + * @brief Retrieve the mime type icon for a file. + */ +EAPI const char *efreet_mime_type_icon_get(const char *mime, const char *theme, + unsigned int size); + +/** + * @brief Clear mime icons mapping cache + */ +EAPI void efreet_mime_type_cache_clear(void); + +/** + * @brief Flush mime icons mapping cache + * + * Flush timeout is defined at compile time by + * EFREET_MIME_ICONS_FLUSH_TIMEOUT + */ +EAPI void efreet_mime_type_cache_flush(void); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/lib/Efreet_Trash.h b/src/lib/Efreet_Trash.h new file mode 100644 index 0000000..3f86aa2 --- /dev/null +++ b/src/lib/Efreet_Trash.h @@ -0,0 +1,102 @@ +#ifndef EFREET_TRASH_H +#define EFREET_TRASH_H + +#ifdef EAPI +# undef EAPI +#endif + +#ifdef _WIN32 +# ifdef EFL_EFREET_TRASH_BUILD +# ifdef DLL_EXPORT +# define EAPI __declspec(dllexport) +# else +# define EAPI +# endif /* ! DLL_EXPORT */ +# else +# define EAPI __declspec(dllimport) +# endif /* ! EFL_EFREET_TRASH_BUILD */ +#else +# ifdef __GNUC__ +# if __GNUC__ >= 4 +# define EAPI __attribute__ ((visibility("default"))) +# else +# define EAPI +# endif +# else +# define EAPI +# endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @file Efreet_Trash.h + * @brief Contains the methods used to support the FDO trash specification. + * @addtogroup Efreet_Trash Efreet_Trash: The XDG Trash Specification + * Efreet_Trash.h provides all of the necessary headers and includes to + * work with Efreet_Trash. + * @{ + */ + +/** + * @return @c 1 on success or @c 0 on failure. + * @brief Initializes the efreet trash system + */ +EAPI int efreet_trash_init(void); + +/** + * @return No value. + * @brief Cleans up the efreet trash system + */ +EAPI int efreet_trash_shutdown(void); + +/** + * @return The XDG Trash local directory or @c NULL on errors. + * Return value must be freed with eina_stringshare_del. + * @brief Retrieves the XDG Trash local directory + */ +EAPI const char *efreet_trash_dir_get(const char *for_file); + +/** + * @param uri The local uri to move in the trash + * @param force_delete If you set this to @c 1 than files on different filesystems + * will be deleted permanently + * @return @c 1 on success, @c 0 on failure or @c -1 in case the uri is not on + * the same filesystem and force_delete is not set. + * @brief This function try to move the given uri to the trash. Files on + * different filesystem can't be moved to trash. If force_delete + * is @c 0 than non-local files will be ignored and @c -1 is returned, if you set + * force_delete to @c 1 non-local files will be deleted without asking. + */ +EAPI int efreet_trash_delete_uri(Efreet_Uri *uri, int force_delete); + +/** + * @return A list of strings with filename (remember to free the list + * when you don't need anymore). + * @brief List all the files and directory currently inside the trash. + */ +EAPI Eina_List *efreet_trash_ls(void); + +/** + * @return @c 1 if the trash is empty or @c 0 if some file are in. + * @brief Check if the trash is currently empty + */ +EAPI int efreet_trash_is_empty(void); + +/** + * @return @c 1 on success or @c 0 on failure. + * @brief Delete all the files inside the trash. + */ +EAPI int efreet_trash_empty_trash(void); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am new file mode 100644 index 0000000..824c8de --- /dev/null +++ b/src/lib/Makefile.am @@ -0,0 +1,69 @@ + +MAINTAINERCLEANFILES = Makefile.in + +lib_LTLIBRARIES = libefreet.la libefreet_mime.la libefreet_trash.la + +INCLUDES = \ +-DLOCALE_DIR=\"@LOCALE_DIR@\" + +EFREETHEADERS = \ +Efreet.h \ +efreet_base.h \ +efreet_desktop.h \ +efreet_icon.h \ +efreet_ini.h \ +efreet_menu.h \ +efreet_utils.h \ +efreet_uri.h + +EFREETSOURCES = \ +efreet.c \ +efreet_base.c \ +efreet_icon.c \ +efreet_xml.c \ +efreet_ini.c \ +efreet_desktop.c \ +efreet_desktop_command.c \ +efreet_menu.c \ +efreet_utils.c \ +efreet_uri.c \ +efreet_cache.c + +includes_HEADERS = $(EFREETHEADERS) Efreet_Mime.h Efreet_Trash.h +includesdir = $(includedir)/efreet-@VMAJ@ + +# Not sure if this was for 'make dist', so left it in but commented - dh +# dist_installed_headers_DATA = $(EFREETHEADERS) Efreet_Mime.h Efreet_Trash.h + +libefreet_la_SOURCES = $(EFREETSOURCES) +libefreet_la_CPPFLAGS = \ +-DPACKAGE_DATA_DIR=\"$(datadir)\" \ +-DPACKAGE_LIB_DIR=\"$(libdir)\" \ +-I$(top_builddir)/src/lib \ +-I$(top_srcdir)/src/lib \ +@EFL_EFREET_BUILD@ \ +@EFREET_CFLAGS@ +libefreet_la_LIBADD = @EFREET_LIBS@ @WIN32_LIBS@ +libefreet_la_LDFLAGS = -no-undefined @lt_enable_auto_import@ -version-info @version_info@ @release_info@ + + +libefreet_mime_la_SOURCES = efreet_mime.c +libefreet_mime_la_CPPFLAGS = \ +-I$(top_builddir)/src/lib \ +-I$(top_srcdir)/src/lib \ +@EFL_EFREET_MIME_BUILD@ \ +@EFREET_CFLAGS@ +libefreet_mime_la_LIBADD = @EFREET_LIBS@ libefreet.la @WIN32_LIBS@ +libefreet_mime_la_LDFLAGS = -no-undefined @lt_enable_auto_import@ -version-info @version_info@ @release_info@ + + +libefreet_trash_la_SOURCES = efreet_trash.c +libefreet_trash_la_CPPFLAGS = \ +-I$(top_builddir)/src/lib \ +-I$(top_srcdir)/src/lib \ +@EFL_EFREET_TRASH_BUILD@ \ +@EFREET_CFLAGS@ +libefreet_trash_la_LIBADD = @EFREET_LIBS@ libefreet.la +libefreet_trash_la_LDFLAGS = -no-undefined @lt_enable_auto_import@ -version-info @version_info@ @release_info@ + +EXTRA_DIST = efreet_private.h efreet_xml.h efreet_cache_private.h diff --git a/src/lib/efreet.c b/src/lib/efreet.c new file mode 100644 index 0000000..c48223f --- /dev/null +++ b/src/lib/efreet.c @@ -0,0 +1,366 @@ +#ifdef HAVE_CONFIG_H +# include +#endif + +#undef alloca +#ifdef HAVE_ALLOCA_H +# include +#elif defined __GNUC__ +# define alloca __builtin_alloca +#elif defined _AIX +# define alloca __alloca +#elif defined _MSC_VER +# include +# define alloca _alloca +#else +# include +# ifdef __cplusplus +extern "C" +# endif +void *alloca (size_t); +#endif + +#include +#include +#include + +#include +#include +#include + +/* define macros and variable for using the eina logging system */ +#define EFREET_MODULE_LOG_DOM /* no logging in this file */ + +#include "Efreet.h" +#include "efreet_private.h" +#include "efreet_xml.h" + +/* + * Needs EAPI because of helper binaries + */ +EAPI int efreet_cache_update = 1; + +static int _efreet_init_count = 0; +static int efreet_parsed_locale = 0; +static const char *efreet_lang = NULL; +static const char *efreet_lang_country = NULL; +static const char *efreet_lang_modifier = NULL; +static void efreet_parse_locale(void); +static int efreet_parse_locale_setting(const char *env); + +#ifndef _WIN32 +static uid_t ruid; +static uid_t rgid; +#endif + +EAPI int +efreet_init(void) +{ +#ifndef _WIN32 + char *tmp; +#endif + + if (++_efreet_init_count != 1) + return _efreet_init_count; + +#ifndef _WIN32 + /* Find users real uid and gid */ + tmp = getenv("SUDO_UID"); + if (tmp) + ruid = strtoul(tmp, NULL, 10); + else + ruid = getuid(); + + tmp = getenv("SUDO_GID"); + if (tmp) + rgid = strtoul(tmp, NULL, 10); + else + rgid = getgid(); +#endif + + if (!eina_init()) + return --_efreet_init_count; + if (!eet_init()) + goto shutdown_eina; + if (!ecore_init()) + goto shutdown_eet; + if (!ecore_file_init()) + goto shutdown_ecore; + + if (!efreet_base_init()) + goto shutdown_ecore_file; + + if (!efreet_cache_init()) + goto shutdown_efreet_base; + + if (!efreet_xml_init()) + goto shutdown_efreet_cache; + + if (!efreet_icon_init()) + goto shutdown_efreet_xml; + + if (!efreet_ini_init()) + goto shutdown_efreet_icon; + + if (!efreet_desktop_init()) + goto shutdown_efreet_ini; + + if (!efreet_menu_init()) + goto shutdown_efreet_desktop; + + if (!efreet_util_init()) + goto shutdown_efreet_menu; + +#ifdef ENABLE_NLS + bindtextdomain(PACKAGE, LOCALE_DIR); + bind_textdomain_codeset(PACKAGE, "UTF-8"); +#endif + + return _efreet_init_count; + +shutdown_efreet_menu: + efreet_menu_shutdown(); +shutdown_efreet_desktop: + efreet_desktop_shutdown(); +shutdown_efreet_ini: + efreet_ini_shutdown(); +shutdown_efreet_icon: + efreet_icon_shutdown(); +shutdown_efreet_xml: + efreet_xml_shutdown(); +shutdown_efreet_cache: + efreet_cache_shutdown(); +shutdown_efreet_base: + efreet_base_shutdown(); +shutdown_ecore_file: + ecore_file_shutdown(); +shutdown_ecore: + ecore_shutdown(); +shutdown_eet: + eet_shutdown(); +shutdown_eina: + eina_shutdown(); + + return --_efreet_init_count; +} + +EAPI int +efreet_shutdown(void) +{ + if (_efreet_init_count <= 0) + { + EINA_LOG_ERR("Init count not greater than 0 in shutdown."); + return 0; + } + if (--_efreet_init_count != 0) + return _efreet_init_count; + + efreet_util_shutdown(); + efreet_menu_shutdown(); + efreet_desktop_shutdown(); + efreet_ini_shutdown(); + efreet_icon_shutdown(); + efreet_xml_shutdown(); + efreet_cache_shutdown(); + efreet_base_shutdown(); + + IF_RELEASE(efreet_lang); + IF_RELEASE(efreet_lang_country); + IF_RELEASE(efreet_lang_modifier); + efreet_parsed_locale = 0; /* reset this in case they init efreet again */ + + ecore_file_shutdown(); + ecore_shutdown(); + eet_shutdown(); + eina_shutdown(); + + return _efreet_init_count; +} + +EAPI void +efreet_lang_reset(void) +{ + IF_RELEASE(efreet_lang); + IF_RELEASE(efreet_lang_country); + IF_RELEASE(efreet_lang_modifier); + efreet_parsed_locale = 0; /* reset this in case they init efreet again */ + + efreet_dirs_reset(); + efreet_cache_desktop_close(); + efreet_cache_desktop_update(); +} + + /** + * @internal + * @return Returns the current users language setting or NULL if none set + * @brief Retrieves the current language setting + */ +const char * +efreet_lang_get(void) +{ + if (efreet_parsed_locale) return efreet_lang; + + efreet_parse_locale(); + return efreet_lang; +} + +/** + * @internal + * @return Returns the current language country setting or NULL if none set + * @brief Retrieves the current country setting for the current language or + */ +const char * +efreet_lang_country_get(void) +{ + if (efreet_parsed_locale) return efreet_lang_country; + + efreet_parse_locale(); + return efreet_lang_country; +} + +/** + * @internal + * @return Returns the current language modifier setting or NULL if none + * set. + * @brief Retrieves the modifier setting for the language. + */ +const char * +efreet_lang_modifier_get(void) +{ + if (efreet_parsed_locale) return efreet_lang_modifier; + + efreet_parse_locale(); + return efreet_lang_modifier; +} + +/** + * @internal + * @return Returns no value + * @brief Parses out the language, country and modifer setting from the + * LC_MESSAGES environment variable + */ +static void +efreet_parse_locale(void) +{ + efreet_parsed_locale = 1; + + if (efreet_parse_locale_setting("LANG")) + return; + + if (efreet_parse_locale_setting("LC_ALL")) + return; + + efreet_parse_locale_setting("LC_MESSAGES"); +} + +/** + * @internal + * @param env The environment variable to grab + * @return Returns 1 if we parsed something of @a env, 0 otherwise + * @brief Tries to parse the lang settings out of the given environment + * variable + */ +static int +efreet_parse_locale_setting(const char *env) +{ + int found = 0; + char *setting; + char *p; + size_t len; + + p = getenv(env); + if (!p) return 0; + len = strlen(p) + 1; + setting = alloca(len); + memcpy(setting, p, len); + + /* pull the modifier off the end */ + p = strrchr(setting, '@'); + if (p) + { + *p = '\0'; + efreet_lang_modifier = eina_stringshare_add(p + 1); + found = 1; + } + + /* if there is an encoding we ignore it */ + p = strrchr(setting, '.'); + if (p) *p = '\0'; + + /* get the country if available */ + p = strrchr(setting, '_'); + if (p) + { + *p = '\0'; + efreet_lang_country = eina_stringshare_add(p + 1); + found = 1; + } + + if (*setting != '\0') + { + efreet_lang = eina_stringshare_add(setting); + found = 1; + } + + return found; +} + +/** + * @internal + * @param buffer The destination buffer + * @param size The destination buffer size + * @param strs The strings to concatenate together + * @return Returns the size of the string in @a buffer + * @brief Concatenates the strings in @a strs into the given @a buffer not + * exceeding the given @a size. + */ +size_t +efreet_array_cat(char *buffer, size_t size, const char *strs[]) +{ + int i; + size_t n; + for (i = 0, n = 0; n < size && strs[i]; i++) + { + n += eina_strlcpy(buffer + n, strs[i], size - n); + } + return n; +} + +#ifndef _WIN32 +EAPI void +efreet_fsetowner(int fd) +{ + struct stat st; + + if (fd < 0) return; + if (fstat(fd, &st) < 0) return; + if (st.st_uid == ruid) return; + + if (fchown(fd, ruid, rgid) != 0) return; +} +#else +EAPI void +efreet_fsetowner(int fd __UNUSED__) +{ +} +#endif + +#ifndef _WIN32 +EAPI void +efreet_setowner(const char *path) +{ + EINA_SAFETY_ON_NULL_RETURN(path); + + int fd; + + fd = open(path, O_RDONLY); + if (fd < 0) return; + efreet_fsetowner(fd); + close(fd); +} +#else +EAPI void +efreet_setowner(const char *path __UNUSED__) +{ +} +#endif diff --git a/src/lib/efreet_base.c b/src/lib/efreet_base.c new file mode 100644 index 0000000..afb5920 --- /dev/null +++ b/src/lib/efreet_base.c @@ -0,0 +1,387 @@ +#ifdef HAVE_CONFIG_H +# include +#endif + +#undef alloca +#ifdef HAVE_ALLOCA_H +# include +#elif defined __GNUC__ +# define alloca __builtin_alloca +#elif defined _AIX +# define alloca __alloca +#elif defined _MSC_VER +# include +# define alloca _alloca +#else +# include +# ifdef __cplusplus +extern "C" +# endif +void *alloca (size_t); +#endif + +#include + +#ifdef _WIN32 +# include +#endif + +/* define macros and variable for using the eina logging system */ +#define EFREET_MODULE_LOG_DOM _efreet_base_log_dom +static int _efreet_base_log_dom = -1; + +#include "Efreet.h" +#include "efreet_private.h" + +static Efreet_Version _version = { VMAJ, VMIN, VMIC, VREV }; +EAPI Efreet_Version *efreet_version = &_version; + +#ifdef _WIN32 +# define EFREET_PATH_SEP ';' +#else +# define EFREET_PATH_SEP ':' +#endif + +static const char *efreet_home_dir = NULL; +static const char *xdg_data_home = NULL; +static const char *xdg_config_home = NULL; +static const char *xdg_cache_home = NULL; +static Eina_List *xdg_data_dirs = NULL; +static Eina_List *xdg_config_dirs = NULL; +static const char *xdg_desktop_dir = NULL; +static const char *hostname = NULL; + +static const char *efreet_dir_get(const char *key, const char *fallback); +static Eina_List *efreet_dirs_get(const char *key, + const char *fallback); +static const char *efreet_user_dir_get(const char *key, const char *fallback); + +/** + * @internal + * @return Returns @c 1 on success or @c 0 on failure + * @brief Initializes the efreet base settings + */ +int +efreet_base_init(void) +{ + _efreet_base_log_dom = eina_log_domain_register + ("efreet_base", EFREET_DEFAULT_LOG_COLOR); + if (_efreet_base_log_dom < 0) + { + EINA_LOG_ERR("Efreet: Could not create a log domain for efreet_base.\n"); + return 0; + } + return 1; +} + +/** + * @internal + * @return Returns no value + * @brief Cleans up the efreet base settings system + */ +void +efreet_base_shutdown(void) +{ + IF_RELEASE(efreet_home_dir); + IF_RELEASE(xdg_desktop_dir); + IF_RELEASE(xdg_data_home); + IF_RELEASE(xdg_config_home); + IF_RELEASE(xdg_cache_home); + + IF_FREE_LIST(xdg_data_dirs, eina_stringshare_del); + IF_FREE_LIST(xdg_config_dirs, eina_stringshare_del); + + IF_RELEASE(hostname); + + eina_log_domain_unregister(_efreet_base_log_dom); + _efreet_base_log_dom = -1; +} + +/** + * @internal + * @return Returns the users home directory + * @brief Gets the users home directory and returns it. + */ +const char * +efreet_home_dir_get(void) +{ + if (efreet_home_dir) return efreet_home_dir; + + efreet_home_dir = getenv("HOME"); +#ifdef _WIN32 + if (!efreet_home_dir || efreet_home_dir[0] == '\0') + efreet_home_dir = getenv("USERPROFILE"); +#endif + if (!efreet_home_dir || efreet_home_dir[0] == '\0') + efreet_home_dir = "/tmp"; + + efreet_home_dir = eina_stringshare_add(efreet_home_dir); + + return efreet_home_dir; +} + +EAPI const char * +efreet_desktop_dir_get(void) +{ + if (xdg_desktop_dir) return xdg_desktop_dir; + xdg_desktop_dir = efreet_user_dir_get("XDG_DESKTOP_DIR", _("Desktop")); + return xdg_desktop_dir; +} + +EAPI const char * +efreet_data_home_get(void) +{ + if (xdg_data_home) return xdg_data_home; + xdg_data_home = efreet_dir_get("XDG_DATA_HOME", "/.local/share"); + return xdg_data_home; +} + +EAPI Eina_List * +efreet_data_dirs_get(void) +{ +#ifdef _WIN32 + char buf[4096]; +#endif + + if (xdg_data_dirs) return xdg_data_dirs; + +#ifdef _WIN32 + snprintf(buf, 4096, "%s\\Efl;" PACKAGE_DATA_DIR ";/usr/share;/usr/local/share", getenv("APPDATA")); + xdg_data_dirs = efreet_dirs_get("XDG_DATA_DIRS", buf); +#else + xdg_data_dirs = efreet_dirs_get("XDG_DATA_DIRS", + PACKAGE_DATA_DIR ":/usr/share:/usr/local/share"); +#endif + return xdg_data_dirs; +} + +EAPI const char * +efreet_config_home_get(void) +{ + if (xdg_config_home) return xdg_config_home; + xdg_config_home = efreet_dir_get("XDG_CONFIG_HOME", "/.config"); + return xdg_config_home; +} + +EAPI Eina_List * +efreet_config_dirs_get(void) +{ + if (xdg_config_dirs) return xdg_config_dirs; + xdg_config_dirs = efreet_dirs_get("XDG_CONFIG_DIRS", "/etc/xdg"); + return xdg_config_dirs; +} + +EAPI const char * +efreet_cache_home_get(void) +{ + if (xdg_cache_home) return xdg_cache_home; + xdg_cache_home = efreet_dir_get("XDG_CACHE_HOME", "/.cache"); + return xdg_cache_home; +} + +EAPI const char * +efreet_hostname_get(void) +{ + char buf[256]; + + if (hostname) return hostname; + if (gethostname(buf, sizeof(buf)) < 0) + hostname = eina_stringshare_add(""); + else + hostname = eina_stringshare_add(buf); + return hostname; +} + +void +efreet_dirs_reset(void) +{ + eina_stringshare_replace(&xdg_desktop_dir, NULL); +} + +/** + * @internal + * @param key The environment key to lookup + * @param fallback The fallback value to use + * @return Returns the directory related to the given key or the fallback + * @brief This tries to determine the correct directory name given the + * environment key @a key and fallbacks @a fallback. + */ +static const char * +efreet_dir_get(const char *key, const char *fallback) +{ + char *dir; + const char *t; + + dir = getenv(key); + if (!dir || dir[0] == '\0') + { + int len; + const char *user; + + user = efreet_home_dir_get(); + len = strlen(user) + strlen(fallback) + 1; + dir = alloca(len); + snprintf(dir, len, "%s%s", user, fallback); + + t = eina_stringshare_add(dir); + } + else t = eina_stringshare_add(dir); + + return t; +} + +/** + * @internal + * @param key The environment key to lookup + * @param fallback The fallback value to use + * @return Returns a list of directories specified by the given key @a key + * or from the list of fallbacks in @a fallback. + * @brief Creates a list of directories as given in the environment key @a + * key or from the fallbacks in @a fallback + */ +static Eina_List * +efreet_dirs_get(const char *key, const char *fallback) +{ + Eina_List *dirs = NULL; + const char *path; + char *tmp, *s, *p; + char ts[PATH_MAX]; + size_t len; + + path = getenv(key); + if (!path || (path[0] == '\0')) path = fallback; + + if (!path) return dirs; + + len = strlen(path) + 1; + tmp = alloca(len); + memcpy(tmp, path, len); + s = tmp; + p = strchr(s, EFREET_PATH_SEP); + while (p) + { + *p = '\0'; + if (!eina_list_search_unsorted(dirs, EINA_COMPARE_CB(strcmp), s)) + { + // resolve path properly/fully to remove path//path2 to + // path/path2, path/./path2 to path/path2 etc. + if (realpath(s, ts)) + dirs = eina_list_append(dirs, (void *)eina_stringshare_add(ts)); + } + + s = ++p; + p = strchr(s, EFREET_PATH_SEP); + } + if (!eina_list_search_unsorted(dirs, EINA_COMPARE_CB(strcmp), s)) + { + // resolve path properly/fully to remove path//path2 to + // path/path2, path/./path2 to path/path2 etc. + if (realpath(s, ts)) + dirs = eina_list_append(dirs, (void *)eina_stringshare_add(ts)); + } + + return dirs; +} + +static const char * +efreet_env_expand(const char *in) +{ + Eina_Strbuf *sb; + const char *ret, *p, *e1 = NULL, *e2 = NULL, *val; + char *env; + + if (!in) return NULL; + sb = eina_strbuf_new(); + if (!sb) return NULL; + + /* maximum length of any env var is the input string */ + env = alloca(strlen(in) + 1); + for (p = in; *p; p++) + { + if (!e1) + { + if (*p == '$') e1 = p + 1; + else eina_strbuf_append_char(sb, *p); + } + else if (!(((*p >= 'a') && (*p <= 'z')) || + ((*p >= 'A') && (*p <= 'Z')) || + ((*p >= '0') && (*p <= '9')) || + (*p == '_'))) + { + size_t len; + + e2 = p; + len = (size_t)(e2 - e1); + if (len > 0) + { + memcpy(env, e1, len); + env[len] = 0; + val = getenv(env); + if (val) eina_strbuf_append(sb, val); + } + e1 = NULL; + eina_strbuf_append_char(sb, *p); + } + } + ret = eina_stringshare_add(eina_strbuf_string_get(sb)); + eina_strbuf_free(sb); + return ret; +} + +/** + * @internal + * @param key The user-dirs key to lookup + * @param fallback The fallback value to use + * @return Returns the directory related to the given key or the fallback + * @brief This tries to determine the correct directory name given the + * user-dirs key @a key and fallbacks @a fallback. + */ +static const char * +efreet_user_dir_get(const char *key, const char *fallback) +{ + Eina_File *file = NULL; + Eina_File_Line *line; + Eina_Iterator *it = NULL; + const char *config_home; + char path[PATH_MAX]; + char *ret = NULL; + + config_home = efreet_config_home_get(); + snprintf(path, sizeof(path), "%s/user-dirs.dirs", config_home); + + file = eina_file_open(path, EINA_FALSE); + if (!file) goto fallback; + it = eina_file_map_lines(file); + if (!it) goto fallback; + EINA_ITERATOR_FOREACH(it, line) + { + const char *eq, *end; + + if (line->length < 3) continue; + if (line->start[0] == '#') continue; + if (strncmp(line->start, "XDG", 3)) continue; + eq = memchr(line->start, '=', line->length); + if (!eq) continue; + if (strncmp(key, line->start, eq - line->start)) continue; + if (++eq >= line->end) continue; + if (*eq != '"') continue; + if (++eq >= line->end) continue; + end = memchr(eq, '"', line->end - eq); + if (!end) continue; + ret = alloca(end - eq + 1); + memcpy(ret, eq, end - eq); + ret[end - eq] = '\0'; + break; + } +fallback: + if (it) eina_iterator_free(it); + if (file) eina_file_close(file); + if (!ret) + { + const char *home; + home = efreet_home_dir_get(); + ret = alloca(strlen(home) + strlen(fallback) + 2); + sprintf(ret, "%s/%s", home, fallback); + } + return efreet_env_expand(ret); +} diff --git a/src/lib/efreet_base.h b/src/lib/efreet_base.h new file mode 100644 index 0000000..0eb3d52 --- /dev/null +++ b/src/lib/efreet_base.h @@ -0,0 +1,86 @@ +#ifndef EFREET_BASE_H +#define EFREET_BASE_H +/** + * @file efreet_base.h + * @brief Contains the methods used to support the FDO base directory + * specification. + * @addtogroup Efreet_Base Efreet_Base: The XDG Base Directory Specification + * functions + * + * @{ + */ + + +/** + * @return Returns the XDG Data Home directory + * @brief Retrieves the XDG Data Home directory + */ +EAPI const char *efreet_data_home_get(void); + +/** + * @return Returns the Eina_List of preference ordered extra data directories + * @brief Returns the Eina_List of preference ordered extra data directories + * + * @note The returned list is static inside Efreet. If you add/remove from the + * list then the next call to efreet_data_dirs_get() will return your + * modified values. DO NOT free this list. + */ +EAPI Eina_List *efreet_data_dirs_get(void); + + +/** + * @return Returns the XDG Config Home directory + * @brief Retrieves the XDG Config Home directory + */ +EAPI const char *efreet_config_home_get(void); + +/** + * @return Returns the XDG Desktop directory + * @brief Retrieves the XDG Desktop directory + * @since 1.3 + */ +EAPI const char *efreet_desktop_dir_get(void); + +/** + * @return Returns the Eina_List of preference ordered extra config directories + * @brief Returns the Eina_List of preference ordered extra config + * directories + * + * @note The returned list is static inside Efreet. If you add/remove from the + * list then the next call to efreet_config_dirs_get() will return your + * modified values. DO NOT free this list. + */ +EAPI Eina_List *efreet_config_dirs_get(void); + + +/** + * @return Returns the XDG Cache Home directory + * @brief Retrieves the XDG Cache Home directory + */ +EAPI const char *efreet_cache_home_get(void); + +/** + * @return Returns the current hostname + * @brief Returns the current hostname or empty string if not found + */ +EAPI const char *efreet_hostname_get(void); + +/** + * Efreet_Event_Cache_Update + */ +typedef struct _Efreet_Event_Cache_Update Efreet_Event_Cache_Update; + +/** + * Efreet_Event_Cache_Update + * @brief event struct sent with EFREET_EVENT_*_CACHE_UPDATE + */ +struct _Efreet_Event_Cache_Update +{ + int dummy; +}; + +/** + * @} + */ + +#endif diff --git a/src/lib/efreet_cache.c b/src/lib/efreet_cache.c new file mode 100644 index 0000000..a53c8e1 --- /dev/null +++ b/src/lib/efreet_cache.c @@ -0,0 +1,1398 @@ +#ifdef HAVE_CONFIG_H +# include +#endif + +/* TODO: Consider flushing local icons cache after idling. + * Icon requests will probably come in batches, f.ex. during menu + * browsing. + */ + +#include +#include +#include +#include + +#include +#include +#include + +/* define macros and variable for using the eina logging system */ +#define EFREET_MODULE_LOG_DOM _efreet_cache_log_dom +static int _efreet_cache_log_dom = -1; + +#include "Efreet.h" +#include "efreet_private.h" +#include "efreet_cache_private.h" + +#define NON_EXISTING (void *)-1 + +typedef struct _Efreet_Old_Cache Efreet_Old_Cache; + +struct _Efreet_Old_Cache +{ + Eina_Hash *hash; + Eet_File *ef; +}; + +/** + * Data for cache files + */ +static Eet_Data_Descriptor *directory_edd = NULL; +static Eet_Data_Descriptor *icon_theme_edd = NULL; +static Eet_Data_Descriptor *icon_theme_directory_edd = NULL; + +static Eet_Data_Descriptor *icon_fallback_edd = NULL; +static Eet_Data_Descriptor *icon_element_pointer_edd = NULL; +static Eet_Data_Descriptor *icon_element_edd = NULL; +static Eet_Data_Descriptor *icon_edd = NULL; + +static Eet_File *icon_cache = NULL; +static Eet_File *fallback_cache = NULL; +static Eet_File *icon_theme_cache = NULL; + +static Eina_Hash *themes = NULL; +static Eina_Hash *icons = NULL; +static Eina_Hash *fallbacks = NULL; + +static const char *icon_theme_cache_file = NULL; + +static const char *theme_name = NULL; + +static Eet_Data_Descriptor *version_edd = NULL; +static Eet_Data_Descriptor *desktop_edd = NULL; +static Eet_Data_Descriptor *hash_array_string_edd = NULL; +static Eet_Data_Descriptor *array_string_edd = NULL; +static Eet_Data_Descriptor *hash_string_edd = NULL; + +static Eina_Hash *desktops = NULL; +static Eina_List *desktop_dirs_add = NULL; +static Eet_File *desktop_cache = NULL; +static const char *desktop_cache_file = NULL; + +static Ecore_File_Monitor *cache_monitor = NULL; + +static Ecore_Event_Handler *cache_exe_handler = NULL; +static Ecore_Timer *icon_cache_timer = NULL; +static Ecore_Exe *icon_cache_exe = NULL; +static int icon_cache_exe_lock = -1; +static Ecore_Timer *desktop_cache_timer = NULL; +static Ecore_Exe *desktop_cache_exe = NULL; +static int desktop_cache_exe_lock = -1; + +static Eina_List *old_desktop_caches = NULL; + +static const char *util_cache_file = NULL; +static Eet_File *util_cache = NULL; +static Efreet_Cache_Hash *util_cache_hash = NULL; +static const char *util_cache_hash_key = NULL; +static Efreet_Cache_Array_String *util_cache_names = NULL; +static const char *util_cache_names_key = NULL; + +static void efreet_cache_edd_shutdown(void); +static void efreet_cache_icon_free(Efreet_Cache_Icon *icon); +static void efreet_cache_icon_fallback_free(Efreet_Cache_Fallback_Icon *icon); +static void efreet_cache_icon_theme_free(Efreet_Icon_Theme *theme); + +static Eina_Bool efreet_cache_check(Eet_File **ef, const char *path, int major); +static void *efreet_cache_close(Eet_File *ef); + +static Eina_Bool cache_exe_cb(void *data, int type, void *event); +static Eina_Bool cache_check_change(const char *path); +static void cache_update_cb(void *data, Ecore_File_Monitor *em, + Ecore_File_Event event, const char *path); + +static Eina_Bool desktop_cache_update_cache_cb(void *data); +static Eina_Bool icon_cache_update_cache_cb(void *data); +static void desktop_cache_update_free(void *data, void *ev); +static void icon_cache_update_free(void *data, void *ev); + +static void *hash_array_string_add(void *hash, const char *key, void *data); + +EAPI int EFREET_EVENT_ICON_CACHE_UPDATE = 0; +EAPI int EFREET_EVENT_DESKTOP_CACHE_UPDATE = 0; +EAPI int EFREET_EVENT_DESKTOP_CACHE_BUILD = 0; + +int +efreet_cache_init(void) +{ + char buf[PATH_MAX]; + + _efreet_cache_log_dom = eina_log_domain_register("efreet_cache", EFREET_DEFAULT_LOG_COLOR); + if (_efreet_cache_log_dom < 0) + return 0; + + EFREET_EVENT_ICON_CACHE_UPDATE = ecore_event_type_new(); + EFREET_EVENT_DESKTOP_CACHE_UPDATE = ecore_event_type_new(); + EFREET_EVENT_DESKTOP_CACHE_BUILD = ecore_event_type_new(); + + themes = eina_hash_string_superfast_new(EINA_FREE_CB(efreet_cache_icon_theme_free)); + icons = eina_hash_string_superfast_new(EINA_FREE_CB(efreet_cache_icon_free)); + fallbacks = eina_hash_string_superfast_new(EINA_FREE_CB(efreet_cache_icon_fallback_free)); + desktops = eina_hash_string_superfast_new(NULL); + + if (efreet_cache_update) + { + snprintf(buf, sizeof(buf), "%s/efreet", efreet_cache_home_get()); + if (!ecore_file_exists(buf)) + { + if (!ecore_file_mkpath(buf)) + { + ERR("Failed to create directory '%s'", buf); + goto error; + } + efreet_setowner(buf); + } + + cache_exe_handler = ecore_event_handler_add(ECORE_EXE_EVENT_DEL, + cache_exe_cb, NULL); + if (!cache_exe_handler) + { + ERR("Failed to add exe del handler"); + goto error; + } + + cache_monitor = ecore_file_monitor_add(buf, + cache_update_cb, + NULL); + if (!cache_monitor) + { + ERR("Failed to set up ecore file monitor for '%s'", buf); + goto error; + } + + efreet_cache_icon_update(); + efreet_cache_desktop_update(); + } + + return 1; +error: + if (themes) eina_hash_free(themes); + themes = NULL; + if (icons) eina_hash_free(icons); + icons = NULL; + if (fallbacks) eina_hash_free(fallbacks); + fallbacks = NULL; + if (desktops) eina_hash_free(desktops); + desktops = NULL; + + if (cache_exe_handler) ecore_event_handler_del(cache_exe_handler); + cache_exe_handler = NULL; + if (cache_monitor) ecore_file_monitor_del(cache_monitor); + cache_monitor = NULL; + efreet_cache_edd_shutdown(); + return 0; +} + +void +efreet_cache_shutdown(void) +{ + Efreet_Old_Cache *d; + void *data; + + IF_RELEASE(theme_name); + + icon_cache = efreet_cache_close(icon_cache); + icon_theme_cache = efreet_cache_close(icon_theme_cache); + + IF_FREE_HASH(themes); + IF_FREE_HASH(icons); + IF_FREE_HASH(fallbacks); + + IF_FREE_HASH_CB(desktops, EINA_FREE_CB(efreet_cache_desktop_free)); + EINA_LIST_FREE(desktop_dirs_add, data) + eina_stringshare_del(data); + desktop_cache = efreet_cache_close(desktop_cache); + IF_RELEASE(desktop_cache_file); + + if (cache_exe_handler) ecore_event_handler_del(cache_exe_handler); + cache_exe_handler = NULL; + if (cache_monitor) ecore_file_monitor_del(cache_monitor); + cache_monitor = NULL; + + efreet_cache_edd_shutdown(); + if (desktop_cache_timer) + { + ecore_timer_del(desktop_cache_timer); + desktop_cache_timer = NULL; + } + IF_RELEASE(icon_theme_cache_file); + if (icon_cache_exe_lock > 0) + { + close(icon_cache_exe_lock); + icon_cache_exe_lock = -1; + } + + if (desktop_cache_exe_lock > 0) + { + close(desktop_cache_exe_lock); + desktop_cache_exe_lock = -1; + } + + if (old_desktop_caches) + ERR("This application has not properly closed all its desktop references!"); + EINA_LIST_FREE(old_desktop_caches, d) + { + eina_hash_free(d->hash); + eet_close(d->ef); + free(d); + } + + IF_RELEASE(util_cache_names_key); + efreet_cache_array_string_free(util_cache_names); + util_cache_names = NULL; + + IF_RELEASE(util_cache_hash_key); + if (util_cache_hash) + { + eina_hash_free(util_cache_hash->hash); + free(util_cache_hash); + util_cache_hash = NULL; + } + + util_cache = efreet_cache_close(util_cache); + IF_RELEASE(util_cache_file); + + eina_log_domain_unregister(_efreet_cache_log_dom); + _efreet_cache_log_dom = -1; +} + +/* + * Needs EAPI because of helper binaries + */ +EAPI const char * +efreet_icon_cache_file(const char *theme) +{ + static char cache_file[PATH_MAX] = { '\0' }; + const char *cache; + + EINA_SAFETY_ON_NULL_RETURN_VAL(theme, NULL); + + cache = efreet_cache_home_get(); + + snprintf(cache_file, sizeof(cache_file), "%s/efreet/icons_%s_%s.eet", cache, theme, efreet_hostname_get()); + + return cache_file; +} + +/* + * Needs EAPI because of helper binaries + */ +EAPI const char * +efreet_icon_theme_cache_file(void) +{ + char tmp[PATH_MAX] = { '\0' }; + + if (icon_theme_cache_file) return icon_theme_cache_file; + + snprintf(tmp, sizeof(tmp), "%s/efreet/icon_themes_%s.eet", + efreet_cache_home_get(), efreet_hostname_get()); + icon_theme_cache_file = eina_stringshare_add(tmp); + + return icon_theme_cache_file; +} + +/* + * Needs EAPI because of helper binaries + */ +EAPI const char * +efreet_desktop_util_cache_file(void) +{ + char tmp[PATH_MAX] = { '\0' }; + const char *cache_dir, *lang, *country, *modifier; + + if (util_cache_file) return util_cache_file; + + cache_dir = efreet_cache_home_get(); + lang = efreet_lang_get(); + country = efreet_lang_country_get(); + modifier = efreet_lang_modifier_get(); + + if (lang && country && modifier) + snprintf(tmp, sizeof(tmp), "%s/efreet/desktop_util_%s_%s_%s@%s.eet", cache_dir, efreet_hostname_get(), lang, country, modifier); + else if (lang && country) + snprintf(tmp, sizeof(tmp), "%s/efreet/desktop_util_%s_%s_%s.eet", cache_dir, efreet_hostname_get(), lang, country); + else if (lang) + snprintf(tmp, sizeof(tmp), "%s/efreet/desktop_util_%s_%s.eet", cache_dir, efreet_hostname_get(), lang); + else + snprintf(tmp, sizeof(tmp), "%s/efreet/desktop_util_%s.eet", cache_dir, efreet_hostname_get()); + + util_cache_file = eina_stringshare_add(tmp); + return util_cache_file; +} + +/* + * Needs EAPI because of helper binaries + */ +EAPI Eet_Data_Descriptor * +efreet_version_edd(void) +{ + Eet_Data_Descriptor_Class eddc; + + if (version_edd) return version_edd; + + EET_EINA_FILE_DATA_DESCRIPTOR_CLASS_SET(&eddc, Efreet_Cache_Version); + version_edd = eet_data_descriptor_file_new(&eddc); + if (!version_edd) return NULL; + + EET_DATA_DESCRIPTOR_ADD_BASIC(version_edd, Efreet_Cache_Version, + "minor", minor, EET_T_UCHAR); + EET_DATA_DESCRIPTOR_ADD_BASIC(version_edd, Efreet_Cache_Version, + "major", major, EET_T_UCHAR); + + return version_edd; +} + +/* + * Needs EAPI because of helper binaries + */ +EAPI Eet_Data_Descriptor * +efreet_hash_array_string_edd(void) +{ + Eet_Data_Descriptor_Class eddc; + + if (hash_array_string_edd) return hash_array_string_edd; + + EET_EINA_FILE_DATA_DESCRIPTOR_CLASS_SET(&eddc, Efreet_Cache_Hash); + eddc.func.hash_add = hash_array_string_add; + hash_array_string_edd = eet_data_descriptor_file_new(&eddc); + if (!hash_array_string_edd) return NULL; + + EET_DATA_DESCRIPTOR_ADD_HASH(hash_array_string_edd, Efreet_Cache_Hash, + "hash", hash, efreet_array_string_edd()); + + return hash_array_string_edd; +} + +/* + * Needs EAPI because of helper binaries + */ +EAPI Eet_Data_Descriptor * +efreet_hash_string_edd(void) +{ + Eet_Data_Descriptor_Class eddc; + + if (hash_string_edd) return hash_string_edd; + + EET_EINA_FILE_DATA_DESCRIPTOR_CLASS_SET(&eddc, Efreet_Cache_Hash); + hash_string_edd = eet_data_descriptor_file_new(&eddc); + if (!hash_string_edd) return NULL; + + EET_DATA_DESCRIPTOR_ADD_HASH_STRING(hash_string_edd, Efreet_Cache_Hash, + "hash", hash); + + return hash_string_edd; +} + +/* + * Needs EAPI because of helper binaries + */ +EAPI Eet_Data_Descriptor * +efreet_array_string_edd(void) +{ + Eet_Data_Descriptor_Class eddc; + + if (array_string_edd) return array_string_edd; + + EET_EINA_FILE_DATA_DESCRIPTOR_CLASS_SET(&eddc, Efreet_Cache_Array_String); + array_string_edd = eet_data_descriptor_file_new(&eddc); + if (!array_string_edd) return NULL; + EET_DATA_DESCRIPTOR_ADD_VAR_ARRAY_STRING(array_string_edd, Efreet_Cache_Array_String, + "array", array); + + return array_string_edd; +} + +/* + * Needs EAPI because of helper binaries + */ +EAPI const char * +efreet_desktop_cache_file(void) +{ + char tmp[PATH_MAX] = { '\0' }; + const char *cache, *lang, *country, *modifier; + + if (desktop_cache_file) return desktop_cache_file; + + cache = efreet_cache_home_get(); + lang = efreet_lang_get(); + country = efreet_lang_country_get(); + modifier = efreet_lang_modifier_get(); + + if (lang && country && modifier) + snprintf(tmp, sizeof(tmp), "%s/efreet/desktop_%s_%s_%s@%s.eet", cache, efreet_hostname_get(), lang, country, modifier); + else if (lang && country) + snprintf(tmp, sizeof(tmp), "%s/efreet/desktop_%s_%s_%s.eet", cache, efreet_hostname_get(), lang, country); + else if (lang) + snprintf(tmp, sizeof(tmp), "%s/efreet/desktop_%s_%s.eet", cache, efreet_hostname_get(), lang); + else + snprintf(tmp, sizeof(tmp), "%s/efreet/desktop_%s.eet", cache, efreet_hostname_get()); + + desktop_cache_file = eina_stringshare_add(tmp); + return desktop_cache_file; +} + +#define EDD_SHUTDOWN(Edd) \ + if (Edd) eet_data_descriptor_free(Edd); \ +Edd = NULL; + +static void +efreet_cache_edd_shutdown(void) +{ + EDD_SHUTDOWN(version_edd); + EDD_SHUTDOWN(desktop_edd); + EDD_SHUTDOWN(hash_array_string_edd); + EDD_SHUTDOWN(array_string_edd); + EDD_SHUTDOWN(hash_string_edd); + EDD_SHUTDOWN(icon_theme_edd); + EDD_SHUTDOWN(icon_theme_directory_edd); + EDD_SHUTDOWN(directory_edd); + EDD_SHUTDOWN(icon_fallback_edd); + EDD_SHUTDOWN(icon_element_pointer_edd); + EDD_SHUTDOWN(icon_element_edd); + EDD_SHUTDOWN(icon_edd); +} + +#define EFREET_POINTER_TYPE(Edd_Dest, Edd_Source, Type) \ +{ \ + typedef struct _Efreet_##Type##_Pointer Efreet_##Type##_Pointer; \ + struct _Efreet_##Type##_Pointer \ + { \ + Efreet_##Type *pointer; \ + }; \ + \ + EET_EINA_FILE_DATA_DESCRIPTOR_CLASS_SET(&eddc, Efreet_##Type##_Pointer); \ + Edd_Dest = eet_data_descriptor_file_new(&eddc); \ + EET_DATA_DESCRIPTOR_ADD_SUB(Edd_Dest, Efreet_##Type##_Pointer, \ + "pointer", pointer, Edd_Source); \ +} + +static Eet_Data_Descriptor * +efreet_icon_directory_edd(void) +{ + Eet_Data_Descriptor_Class eddc; + + if (directory_edd) return directory_edd; + + EET_EINA_FILE_DATA_DESCRIPTOR_CLASS_SET(&eddc, Efreet_Cache_Directory); + directory_edd = eet_data_descriptor_file_new(&eddc); + if (!directory_edd) return NULL; + + EET_DATA_DESCRIPTOR_ADD_BASIC(directory_edd, Efreet_Cache_Directory, + "modified_time", modified_time, EET_T_LONG_LONG); + + return directory_edd; +} + +/* + * Needs EAPI because of helper binaries + */ +EAPI Eet_Data_Descriptor * +efreet_icon_edd(void) +{ + Eet_Data_Descriptor_Class eddc; + + if (icon_edd) return icon_edd; + + EET_EINA_FILE_DATA_DESCRIPTOR_CLASS_SET(&eddc, Efreet_Cache_Icon_Element); + icon_element_edd = eet_data_descriptor_file_new(&eddc); + if (!icon_element_edd) return NULL; + + EET_DATA_DESCRIPTOR_ADD_BASIC(icon_element_edd, Efreet_Cache_Icon_Element, + "type", type, EET_T_USHORT); + EET_DATA_DESCRIPTOR_ADD_BASIC(icon_element_edd, Efreet_Cache_Icon_Element, + "normal", normal, EET_T_USHORT); + EET_DATA_DESCRIPTOR_ADD_BASIC(icon_element_edd, Efreet_Cache_Icon_Element, + "normal", normal, EET_T_USHORT); + EET_DATA_DESCRIPTOR_ADD_BASIC(icon_element_edd, Efreet_Cache_Icon_Element, + "min", min, EET_T_USHORT); + EET_DATA_DESCRIPTOR_ADD_BASIC(icon_element_edd, Efreet_Cache_Icon_Element, + "max", max, EET_T_USHORT); + EET_DATA_DESCRIPTOR_ADD_VAR_ARRAY_STRING(icon_element_edd, Efreet_Cache_Icon_Element, + "paths", paths); + + EFREET_POINTER_TYPE(icon_element_pointer_edd, icon_element_edd, Cache_Icon_Element); + + EET_EINA_FILE_DATA_DESCRIPTOR_CLASS_SET(&eddc, Efreet_Cache_Icon); + icon_edd = eet_data_descriptor_file_new(&eddc); + if (!icon_edd) return NULL; + + EET_DATA_DESCRIPTOR_ADD_BASIC(icon_edd, Efreet_Cache_Icon, + "theme", theme, EET_T_STRING); + EET_DATA_DESCRIPTOR_ADD_VAR_ARRAY(icon_edd, Efreet_Cache_Icon, + "icons", icons, icon_element_pointer_edd); + + return icon_edd; +} + +/* + * Needs EAPI because of helper binaries + */ +EAPI Eet_Data_Descriptor * +efreet_icon_theme_edd(Eina_Bool cache) +{ + Eet_Data_Descriptor_Class eddc; + + if (icon_theme_edd) return icon_theme_edd; + + EET_EINA_FILE_DATA_DESCRIPTOR_CLASS_SET(&eddc, Efreet_Icon_Theme_Directory); + icon_theme_directory_edd = eet_data_descriptor_file_new(&eddc); + if (!icon_theme_directory_edd) return NULL; + + EET_DATA_DESCRIPTOR_ADD_BASIC(icon_theme_directory_edd, Efreet_Icon_Theme_Directory, + "name", name, EET_T_STRING); + EET_DATA_DESCRIPTOR_ADD_BASIC(icon_theme_directory_edd, Efreet_Icon_Theme_Directory, + "context", context, EET_T_UCHAR); + EET_DATA_DESCRIPTOR_ADD_BASIC(icon_theme_directory_edd, Efreet_Icon_Theme_Directory, + "type", type, EET_T_UCHAR); + EET_DATA_DESCRIPTOR_ADD_BASIC(icon_theme_directory_edd, Efreet_Icon_Theme_Directory, + "size.normal", size.normal, EET_T_UINT); + EET_DATA_DESCRIPTOR_ADD_BASIC(icon_theme_directory_edd, Efreet_Icon_Theme_Directory, + "size.min", size.min, EET_T_UINT); + EET_DATA_DESCRIPTOR_ADD_BASIC(icon_theme_directory_edd, Efreet_Icon_Theme_Directory, + "size.max", size.max, EET_T_UINT); + EET_DATA_DESCRIPTOR_ADD_BASIC(icon_theme_directory_edd, Efreet_Icon_Theme_Directory, + "size.threshold", size.threshold, EET_T_UINT); + + EET_EINA_FILE_DATA_DESCRIPTOR_CLASS_SET(&eddc, Efreet_Cache_Icon_Theme); + icon_theme_edd = eet_data_descriptor_file_new(&eddc); + if (!icon_theme_edd) return NULL; + + EET_DATA_DESCRIPTOR_ADD_BASIC(icon_theme_edd, Efreet_Cache_Icon_Theme, + "name.internal", theme.name.internal, EET_T_STRING); + EET_DATA_DESCRIPTOR_ADD_BASIC(icon_theme_edd, Efreet_Cache_Icon_Theme, + "name.name", theme.name.name, EET_T_STRING); + EET_DATA_DESCRIPTOR_ADD_BASIC(icon_theme_edd, Efreet_Cache_Icon_Theme, + "comment", theme.comment, EET_T_STRING); + EET_DATA_DESCRIPTOR_ADD_BASIC(icon_theme_edd, Efreet_Cache_Icon_Theme, + "example_icon", theme.example_icon, EET_T_STRING); + + eet_data_descriptor_element_add(icon_theme_edd, "paths", EET_T_STRING, EET_G_LIST, + offsetof(Efreet_Cache_Icon_Theme, theme.paths), 0, NULL, NULL); + eet_data_descriptor_element_add(icon_theme_edd, "inherits", EET_T_STRING, EET_G_LIST, + offsetof(Efreet_Cache_Icon_Theme, theme.inherits), 0, NULL, NULL); + EET_DATA_DESCRIPTOR_ADD_LIST(icon_theme_edd, Efreet_Cache_Icon_Theme, + "directories", theme.directories, icon_theme_directory_edd); + + if (cache) + { + EET_DATA_DESCRIPTOR_ADD_BASIC(icon_theme_edd, Efreet_Cache_Icon_Theme, + "last_cache_check", last_cache_check, EET_T_LONG_LONG); + + EET_DATA_DESCRIPTOR_ADD_BASIC(icon_theme_edd, Efreet_Cache_Icon_Theme, + "path", path, EET_T_STRING); + + EET_DATA_DESCRIPTOR_ADD_HASH(icon_theme_edd, Efreet_Cache_Icon_Theme, + "dirs", dirs, efreet_icon_directory_edd()); + } + + return icon_theme_edd; +} + +/* + * Needs EAPI because of helper binaries + */ +EAPI Eet_Data_Descriptor * +efreet_icon_fallback_edd(void) +{ + Eet_Data_Descriptor_Class eddc; + + if (icon_fallback_edd) return icon_fallback_edd; + + EET_EINA_FILE_DATA_DESCRIPTOR_CLASS_SET(&eddc, Efreet_Cache_Fallback_Icon); + icon_fallback_edd = eet_data_descriptor_file_new(&eddc); + if (!icon_fallback_edd) return NULL; + + EET_DATA_DESCRIPTOR_ADD_VAR_ARRAY_STRING(icon_fallback_edd, + Efreet_Cache_Fallback_Icon, "icons", icons); + + return icon_fallback_edd; +} + +/* + * Needs EAPI because of helper binaries + */ +EAPI Eet_Data_Descriptor * +efreet_desktop_edd(void) +{ + Eet_Data_Descriptor_Class eddc; + + if (desktop_edd) return desktop_edd; + + EET_EINA_FILE_DATA_DESCRIPTOR_CLASS_SET(&eddc, Efreet_Cache_Desktop); + desktop_edd = eet_data_descriptor_file_new(&eddc); + if (!desktop_edd) return NULL; + + EET_DATA_DESCRIPTOR_ADD_BASIC(desktop_edd, Efreet_Cache_Desktop, "type", desktop.type, EET_T_INT); + EET_DATA_DESCRIPTOR_ADD_BASIC(desktop_edd, Efreet_Cache_Desktop, "version", desktop.version, EET_T_STRING); + EET_DATA_DESCRIPTOR_ADD_BASIC(desktop_edd, Efreet_Cache_Desktop, "orig_path", desktop.orig_path, EET_T_STRING); + EET_DATA_DESCRIPTOR_ADD_BASIC(desktop_edd, Efreet_Cache_Desktop, "load_time", desktop.load_time, EET_T_LONG_LONG); + EET_DATA_DESCRIPTOR_ADD_BASIC(desktop_edd, Efreet_Cache_Desktop, "name", desktop.name, EET_T_STRING); + EET_DATA_DESCRIPTOR_ADD_BASIC(desktop_edd, Efreet_Cache_Desktop, "generic_name", desktop.generic_name, EET_T_STRING); + EET_DATA_DESCRIPTOR_ADD_BASIC(desktop_edd, Efreet_Cache_Desktop, "comment", desktop.comment, EET_T_STRING); + EET_DATA_DESCRIPTOR_ADD_BASIC(desktop_edd, Efreet_Cache_Desktop, "icon", desktop.icon, EET_T_STRING); + EET_DATA_DESCRIPTOR_ADD_BASIC(desktop_edd, Efreet_Cache_Desktop, "try_exec", desktop.try_exec, EET_T_STRING); + EET_DATA_DESCRIPTOR_ADD_BASIC(desktop_edd, Efreet_Cache_Desktop, "exec", desktop.exec, EET_T_STRING); + EET_DATA_DESCRIPTOR_ADD_BASIC(desktop_edd, Efreet_Cache_Desktop, "path", desktop.path, EET_T_STRING); + EET_DATA_DESCRIPTOR_ADD_BASIC(desktop_edd, Efreet_Cache_Desktop, "startup_wm_class", desktop.startup_wm_class, EET_T_STRING); + EET_DATA_DESCRIPTOR_ADD_BASIC(desktop_edd, Efreet_Cache_Desktop, "url", desktop.url, EET_T_STRING); + eet_data_descriptor_element_add(desktop_edd, "only_show_in", EET_T_STRING, EET_G_LIST, offsetof(Efreet_Cache_Desktop, desktop.only_show_in), 0, NULL, NULL); + eet_data_descriptor_element_add(desktop_edd, "not_show_in", EET_T_STRING, EET_G_LIST, offsetof(Efreet_Cache_Desktop, desktop.not_show_in), 0, NULL, NULL); + eet_data_descriptor_element_add(desktop_edd, "categories", EET_T_STRING, EET_G_LIST, offsetof(Efreet_Cache_Desktop, desktop.categories), 0, NULL, NULL); + eet_data_descriptor_element_add(desktop_edd, "mime_types", EET_T_STRING, EET_G_LIST, offsetof(Efreet_Cache_Desktop, desktop.mime_types), 0, NULL, NULL); + eet_data_descriptor_element_add(desktop_edd, "x", EET_T_STRING, EET_G_HASH, offsetof(Efreet_Cache_Desktop, desktop.x), 0, NULL, NULL); + EET_DATA_DESCRIPTOR_ADD_BASIC(desktop_edd, Efreet_Cache_Desktop, "no_display", desktop.no_display, EET_T_UCHAR); + EET_DATA_DESCRIPTOR_ADD_BASIC(desktop_edd, Efreet_Cache_Desktop, "hidden", desktop.hidden, EET_T_UCHAR); + EET_DATA_DESCRIPTOR_ADD_BASIC(desktop_edd, Efreet_Cache_Desktop, "terminal", desktop.terminal, EET_T_UCHAR); + EET_DATA_DESCRIPTOR_ADD_BASIC(desktop_edd, Efreet_Cache_Desktop, "startup_notify", desktop.startup_notify, EET_T_UCHAR); + + return desktop_edd; +} + +Efreet_Cache_Icon * +efreet_cache_icon_find(Efreet_Icon_Theme *theme, const char *icon) +{ + Efreet_Cache_Icon *cache = NULL; + + if (theme_name && strcmp(theme_name, theme->name.internal)) + { + /* FIXME: this is bad if people have pointer to this cache, things will go wrong */ + INF("theme_name change from `%s` to `%s`", theme_name, theme->name.internal); + IF_RELEASE(theme_name); + icon_cache = efreet_cache_close(icon_cache); + eina_hash_free(icons); + icons = eina_hash_string_superfast_new(EINA_FREE_CB(efreet_cache_icon_free)); + } + + if (!efreet_cache_check(&icon_cache, efreet_icon_cache_file(theme->name.internal), EFREET_ICON_CACHE_MAJOR)) return NULL; + if (!theme_name) + theme_name = eina_stringshare_add(theme->name.internal); + + cache = eina_hash_find(icons, icon); + if (cache == NON_EXISTING) return NULL; + if (cache) return cache; + + cache = eet_data_read(icon_cache, efreet_icon_edd(), icon); + if (cache) + eina_hash_add(icons, icon, cache); + else + eina_hash_add(icons, icon, NON_EXISTING); + return cache; +} + +Efreet_Cache_Fallback_Icon * +efreet_cache_icon_fallback_find(const char *icon) +{ + Efreet_Cache_Fallback_Icon *cache; + + if (!efreet_cache_check(&fallback_cache, efreet_icon_cache_file(EFREET_CACHE_ICON_FALLBACK), EFREET_ICON_CACHE_MAJOR)) return NULL; + + cache = eina_hash_find(fallbacks, icon); + if (cache == NON_EXISTING) return NULL; + if (cache) return cache; + + cache = eet_data_read(fallback_cache, efreet_icon_fallback_edd(), icon); + if (cache) + eina_hash_add(fallbacks, icon, cache); + else + eina_hash_add(fallbacks, icon, NON_EXISTING); + return cache; +} + +Efreet_Icon_Theme * +efreet_cache_icon_theme_find(const char *theme) +{ + Efreet_Cache_Icon_Theme *cache; + + if (!efreet_cache_check(&icon_theme_cache, efreet_icon_theme_cache_file(), EFREET_ICON_CACHE_MAJOR)) return NULL; + + cache = eina_hash_find(themes, theme); + if (cache == NON_EXISTING) return NULL; + if (cache) return &(cache->theme); + + cache = eet_data_read(icon_theme_cache, efreet_icon_theme_edd(EINA_FALSE), theme); + if (cache) + { + eina_hash_add(themes, theme, cache); + return &(cache->theme); + } + else + eina_hash_add(themes, theme, NON_EXISTING); + return NULL; +} + +static void +efreet_cache_icon_free(Efreet_Cache_Icon *icon) +{ + unsigned int i; + + if (!icon) return; + if (icon == NON_EXISTING) return; + + for (i = 0; i < icon->icons_count; ++i) + { + free(icon->icons[i]->paths); + free(icon->icons[i]); + } + + free(icon->icons); + free(icon); +} + +static void +efreet_cache_icon_fallback_free(Efreet_Cache_Fallback_Icon *icon) +{ + if (!icon) return; + if (icon == NON_EXISTING) return; + + free(icon->icons); + free(icon); +} + +static void +efreet_cache_icon_theme_free(Efreet_Icon_Theme *theme) +{ + void *data; + + if (!theme) return; + if (theme == NON_EXISTING) return; + + eina_list_free(theme->paths); + eina_list_free(theme->inherits); + EINA_LIST_FREE(theme->directories, data) + free(data); + + free(theme); +} + +Eina_List * +efreet_cache_icon_theme_list(void) +{ + Eina_List *ret = NULL; + char **keys; + int i, num; + + if (!efreet_cache_check(&icon_theme_cache, efreet_icon_theme_cache_file(), EFREET_ICON_CACHE_MAJOR)) return NULL; + keys = eet_list(icon_theme_cache, "*", &num); + for (i = 0; i < num; i++) + { + Efreet_Icon_Theme *theme; + if (!strncmp(keys[i], "__efreet", 8)) continue; + + theme = eina_hash_find(themes, keys[i]); + if (!theme) + theme = efreet_cache_icon_theme_find(keys[i]); + if (theme && theme != NON_EXISTING) + ret = eina_list_append(ret, theme); + } + free(keys); + return ret; +} + +/* + * Needs EAPI because of helper binaries + */ +EAPI void +efreet_cache_array_string_free(Efreet_Cache_Array_String *array) +{ + if (!array) return; + free(array->array); + free(array); +} + +Efreet_Desktop * +efreet_cache_desktop_find(const char *file) +{ + Efreet_Cache_Desktop *cache; + char rp[PATH_MAX]; + + if (!realpath(file, rp)) return NULL; + + if (!efreet_cache_check(&desktop_cache, efreet_desktop_cache_file(), EFREET_DESKTOP_CACHE_MAJOR)) return NULL; + + cache = eina_hash_find(desktops, rp); + if (cache == NON_EXISTING) return NULL; + if (cache) + { + /* If less than one second since last stat, return desktop */ + if ((ecore_time_get() - cache->check_time) < 1) + { + INF("Return without stat %f %f", ecore_time_get(), cache->check_time); + return &cache->desktop; + } + if (cache->desktop.load_time == ecore_file_mod_time(cache->desktop.orig_path)) + { + INF("Return with stat %f %f", ecore_time_get(), cache->check_time); + cache->check_time = ecore_time_get(); + return &cache->desktop; + } + + /* We got stale data. The desktop will be free'd eventually as + * users will call efreet_desktop_free */ + eina_hash_set(desktops, rp, NON_EXISTING); + cache = NULL; + } + + cache = eet_data_read(desktop_cache, efreet_desktop_edd(), rp); + if (cache) + { + if (cache->desktop.load_time != ecore_file_mod_time(cache->desktop.orig_path)) + { + /* Don't return stale data */ + INF("We got stale data in the desktop cache"); + efreet_cache_desktop_free(&cache->desktop); + eina_hash_set(desktops, rp, NON_EXISTING); + } + else + { + cache->desktop.eet = 1; + cache->check_time = ecore_time_get(); + eina_hash_set(desktops, cache->desktop.orig_path, cache); + return &cache->desktop; + } + } + else + eina_hash_set(desktops, rp, NON_EXISTING); + return NULL; +} + +void +efreet_cache_desktop_free(Efreet_Desktop *desktop) +{ + Efreet_Old_Cache *d; + Efreet_Desktop *curr; + Eina_List *l; + + if (!desktop || + desktop == NON_EXISTING || + !desktop->eet) return; + + curr = eina_hash_find(desktops, desktop->orig_path); + if (curr == desktop) + { + INF("Found in current cache, purge\n"); + eina_hash_del_by_key(desktops, desktop->orig_path); + } + + EINA_LIST_FOREACH(old_desktop_caches, l, d) + { + curr = eina_hash_find(d->hash, desktop->orig_path); + if (curr == desktop) + { + INF("Found in old cache, purge\n"); + eina_hash_del_by_key(d->hash, desktop->orig_path); + if (eina_hash_population(d->hash) == 0) + { + INF("Cache empty, close file\n"); + eina_hash_free(d->hash); + eet_close(d->ef); + free(d); + old_desktop_caches = eina_list_remove_list(old_desktop_caches, l); + } + break; + } + } + + eina_list_free(desktop->only_show_in); + eina_list_free(desktop->not_show_in); + eina_list_free(desktop->categories); + eina_list_free(desktop->mime_types); + IF_FREE_HASH(desktop->x); + free(desktop); +} + +void +efreet_cache_desktop_add(Efreet_Desktop *desktop) +{ + char buf[PATH_MAX]; + char *dir; + Efreet_Cache_Array_String *arr; + + /* + * Read file from disk, save path in cache so it will be included in next + * cache update + */ + strncpy(buf, desktop->orig_path, PATH_MAX); + buf[PATH_MAX - 1] = '\0'; + dir = dirname(buf); + arr = efreet_cache_desktop_dirs(); + if (arr) + { + unsigned int i; + + for (i = 0; i < arr->array_count; i++) + { + /* Check if we already have this dir in cache */ + if (!strcmp(dir, arr->array[i])) + return; + } + efreet_cache_array_string_free(arr); + } + if (!eina_list_search_unsorted_list(desktop_dirs_add, EINA_COMPARE_CB(strcmp), dir)) + desktop_dirs_add = eina_list_append(desktop_dirs_add, eina_stringshare_add(dir)); + + efreet_cache_desktop_update(); +} + +Efreet_Cache_Array_String * +efreet_cache_desktop_dirs(void) +{ + if (!efreet_cache_check(&desktop_cache, efreet_desktop_cache_file(), EFREET_DESKTOP_CACHE_MAJOR)) return NULL; + + return eet_data_read(desktop_cache, efreet_array_string_edd(), EFREET_CACHE_DESKTOP_DIRS); +} + +void +efreet_cache_desktop_update(void) +{ + if (!efreet_cache_update) return; + + if (desktop_cache_timer) + ecore_timer_delay(desktop_cache_timer, 0.2); + else + desktop_cache_timer = ecore_timer_add(0.2, desktop_cache_update_cache_cb, NULL); +} + +void +efreet_cache_desktop_close(void) +{ + IF_RELEASE(util_cache_names_key); + IF_RELEASE(util_cache_hash_key); + + if ((desktop_cache) && (desktop_cache != NON_EXISTING)) + { + Efreet_Old_Cache *d = NEW(Efreet_Old_Cache, 1); + if (d) + { + d->hash = desktops; + d->ef = desktop_cache; + old_desktop_caches = eina_list_append(old_desktop_caches, d); + } + + desktops = eina_hash_string_superfast_new(NULL); + } + desktop_cache = NULL; + + efreet_cache_array_string_free(util_cache_names); + util_cache_names = NULL; + + if (util_cache_hash) + { + eina_hash_free(util_cache_hash->hash); + free(util_cache_hash); + util_cache_hash = NULL; + } + + util_cache = efreet_cache_close(util_cache); + + IF_RELEASE(desktop_cache_file); + IF_RELEASE(util_cache_file); +} + +void +efreet_cache_icon_update(void) +{ + if (!efreet_cache_update) return; + + if (icon_cache_timer) + ecore_timer_delay(icon_cache_timer, 0.2); + else + icon_cache_timer = ecore_timer_add(0.2, icon_cache_update_cache_cb, NULL); +} + +static Eina_Bool +efreet_cache_check(Eet_File **ef, const char *path, int major) +{ + Efreet_Cache_Version *version; + + if (*ef == NON_EXISTING) return EINA_FALSE; + if (*ef) return EINA_TRUE; + if (!*ef) + *ef = eet_open(path, EET_FILE_MODE_READ); + if (!*ef) + { + *ef = NON_EXISTING; + return EINA_FALSE; + } + + version = eet_data_read(*ef, efreet_version_edd(), EFREET_CACHE_VERSION); + if ((!version) || (version->major != major)) + { + IF_FREE(version); + eet_close(*ef); + *ef = NON_EXISTING; + return EINA_FALSE; + } + free(version); + return EINA_TRUE; +} + +static void * +efreet_cache_close(Eet_File *ef) +{ + if (ef && ef != NON_EXISTING) + eet_close(ef); + return NULL; +} + +Efreet_Cache_Hash * +efreet_cache_util_hash_string(const char *key) +{ + if (util_cache_hash_key && !strcmp(key, util_cache_hash_key)) + return util_cache_hash; + if (!efreet_cache_check(&util_cache, efreet_desktop_util_cache_file(), EFREET_DESKTOP_UTILS_CACHE_MAJOR)) return NULL; + + if (util_cache_hash) + { + /* free previous util_cache */ + IF_RELEASE(util_cache_hash_key); + eina_hash_free(util_cache_hash->hash); + free(util_cache_hash); + } + util_cache_hash_key = eina_stringshare_add(key); + util_cache_hash = eet_data_read(util_cache, efreet_hash_string_edd(), key); + return util_cache_hash; +} + +Efreet_Cache_Hash * +efreet_cache_util_hash_array_string(const char *key) +{ + if (util_cache_hash_key && !strcmp(key, util_cache_hash_key)) + return util_cache_hash; + if (!efreet_cache_check(&util_cache, efreet_desktop_util_cache_file(), EFREET_DESKTOP_UTILS_CACHE_MAJOR)) return NULL; + + IF_RELEASE(util_cache_hash_key); + if (util_cache_hash) + { + /* free previous cache */ + eina_hash_free(util_cache_hash->hash); + free(util_cache_hash); + } + util_cache_hash_key = eina_stringshare_add(key); + util_cache_hash = eet_data_read(util_cache, efreet_hash_array_string_edd(), key); + return util_cache_hash; +} + +Efreet_Cache_Array_String * +efreet_cache_util_names(const char *key) +{ + if (util_cache_names_key && !strcmp(key, util_cache_names_key)) + return util_cache_names; + if (!efreet_cache_check(&util_cache, efreet_desktop_util_cache_file(), EFREET_DESKTOP_UTILS_CACHE_MAJOR)) return NULL; + + if (util_cache_names) + { + /* free previous util_cache */ + IF_RELEASE(util_cache_names_key); + efreet_cache_array_string_free(util_cache_names); + } + util_cache_names_key = eina_stringshare_add(key); + util_cache_names = eet_data_read(util_cache, efreet_array_string_edd(), key); + return util_cache_names; +} + +static Eina_Bool +cache_exe_cb(void *data __UNUSED__, int type __UNUSED__, void *event) +{ + Ecore_Exe_Event_Del *ev; + + ev = event; + if (ev->exe == desktop_cache_exe) + { + if (desktop_cache_exe_lock > 0) + { + close(desktop_cache_exe_lock); + desktop_cache_exe_lock = -1; + } + desktop_cache_exe = NULL; + } + else if (ev->exe == icon_cache_exe) + { + if (icon_cache_exe_lock > 0) + { + close(icon_cache_exe_lock); + icon_cache_exe_lock = -1; + } + icon_cache_exe = NULL; + } + return ECORE_CALLBACK_RENEW; +} + +static Eina_Bool +cache_check_change(const char *path) +{ + const char *data; + Eina_Bool changed = EINA_TRUE; + Eina_File *f; + + f = eina_file_open(path, EINA_FALSE); + if (!f) return EINA_TRUE; + if (eina_file_size_get(f) < 1) return EINA_TRUE; + data = eina_file_map_all(f, EINA_FILE_SEQUENTIAL); + if (*data == 'n') changed = EINA_FALSE; + eina_file_close(f); + return changed; +} + +static void +cache_update_cb(void *data __UNUSED__, Ecore_File_Monitor *em __UNUSED__, + Ecore_File_Event event, const char *path) +{ + const char *file; + Efreet_Event_Cache_Update *ev = NULL; + Efreet_Old_Cache *d = NULL; + Eina_List *l = NULL; + + if (event != ECORE_FILE_EVENT_CLOSED) + return; + + file = ecore_file_file_get(path); + if (!file) return; + if (!strcmp(file, "desktop_data.update")) + { + if (cache_check_change(path)) + { + ev = NEW(Efreet_Event_Cache_Update, 1); + if (!ev) goto error; + + efreet_cache_desktop_close(); + + ecore_event_add(EFREET_EVENT_DESKTOP_CACHE_UPDATE, ev, desktop_cache_update_free, d); + } + ecore_event_add(EFREET_EVENT_DESKTOP_CACHE_BUILD, NULL, NULL, NULL); + /* TODO: Check if desktop_dirs_add exists, and rebuild cache if */ + } + else if (!strcmp(file, "icon_data.update")) + { + if (cache_check_change(path)) + { + ev = NEW(Efreet_Event_Cache_Update, 1); + if (!ev) goto error; + + IF_RELEASE(theme_name); + + /* Save all old caches */ + d = NEW(Efreet_Old_Cache, 1); + if (!d) goto error; + d->hash = themes; + d->ef = icon_theme_cache; + l = eina_list_append(l, d); + + d = NEW(Efreet_Old_Cache, 1); + if (!d) goto error; + d->hash = icons; + d->ef = icon_cache; + l = eina_list_append(l, d); + + d = NEW(Efreet_Old_Cache, 1); + if (!d) goto error; + d->hash = fallbacks; + d->ef = fallback_cache; + l = eina_list_append(l, d); + + /* Create new empty caches */ + themes = eina_hash_string_superfast_new(EINA_FREE_CB(efreet_cache_icon_theme_free)); + icons = eina_hash_string_superfast_new(EINA_FREE_CB(efreet_cache_icon_free)); + fallbacks = eina_hash_string_superfast_new(EINA_FREE_CB(efreet_cache_icon_fallback_free)); + + icon_theme_cache = NULL; + icon_cache = NULL; + fallback_cache = NULL; + + /* Send event */ + ecore_event_add(EFREET_EVENT_ICON_CACHE_UPDATE, ev, icon_cache_update_free, l); + } + } + return; +error: + IF_FREE(ev); + IF_FREE(d); + EINA_LIST_FREE(l, d) + free(d); +} + +static Eina_Bool +desktop_cache_update_cache_cb(void *data __UNUSED__) +{ + char file[PATH_MAX]; + struct flock fl; + int prio; + + desktop_cache_timer = NULL; + + /* TODO: Retry update cache later */ + if (desktop_cache_exe_lock > 0) return ECORE_CALLBACK_CANCEL; + + snprintf(file, sizeof(file), "%s/efreet/desktop_exec.lock", efreet_cache_home_get()); + + desktop_cache_exe_lock = open(file, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); + if (desktop_cache_exe_lock < 0) goto error; + efreet_fsetowner(desktop_cache_exe_lock); + memset(&fl, 0, sizeof(struct flock)); + fl.l_type = F_WRLCK; + fl.l_whence = SEEK_SET; + if (fcntl(desktop_cache_exe_lock, F_SETLK, &fl) < 0) goto error; + prio = ecore_exe_run_priority_get(); + ecore_exe_run_priority_set(19); + eina_strlcpy(file, PACKAGE_LIB_DIR "/efreet/efreet_desktop_cache_create", sizeof(file)); + if (desktop_dirs_add) + { + const char *str; + + eina_strlcat(file, " -d", sizeof(file)); + EINA_LIST_FREE(desktop_dirs_add, str) + { + eina_strlcat(file, " ", sizeof(file)); + eina_strlcat(file, str, sizeof(file)); + eina_stringshare_del(str); + } + } + INF("Run desktop cache creation: %s", file); + desktop_cache_exe = ecore_exe_run(file, NULL); + ecore_exe_run_priority_set(prio); + if (!desktop_cache_exe) goto error; + + return ECORE_CALLBACK_CANCEL; +error: + if (desktop_cache_exe_lock > 0) + { + close(desktop_cache_exe_lock); + desktop_cache_exe_lock = -1; + } + return ECORE_CALLBACK_CANCEL; +} + +static Eina_Bool +icon_cache_update_cache_cb(void *data __UNUSED__) +{ + char file[PATH_MAX]; + struct flock fl; + int prio; + Eina_List **l, *l2; + + icon_cache_timer = NULL; + + /* TODO: Retry update cache later */ + if (icon_cache_exe_lock > 0) return ECORE_CALLBACK_CANCEL; + + snprintf(file, sizeof(file), "%s/efreet/icon_exec.lock", efreet_cache_home_get()); + + icon_cache_exe_lock = open(file, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); + if (icon_cache_exe_lock < 0) goto error; + efreet_fsetowner(icon_cache_exe_lock); + memset(&fl, 0, sizeof(struct flock)); + fl.l_type = F_WRLCK; + fl.l_whence = SEEK_SET; + if (fcntl(icon_cache_exe_lock, F_SETLK, &fl) < 0) goto error; + prio = ecore_exe_run_priority_get(); + ecore_exe_run_priority_set(19); + eina_strlcpy(file, PACKAGE_LIB_DIR "/efreet/efreet_icon_cache_create", sizeof(file)); + l = efreet_icon_extra_list_get(); + if (l && eina_list_count(*l) > 0) + { + Eina_List *ll; + char *p; + + eina_strlcat(file, " -d", sizeof(file)); + EINA_LIST_FOREACH(*l, ll, p) + { + eina_strlcat(file, " ", sizeof(file)); + eina_strlcat(file, p, sizeof(file)); + } + } + l2 = efreet_icon_extensions_list_get(); + if (eina_list_count(l2) > 0) + { + Eina_List *ll; + char *p; + + eina_strlcat(file, " -e", sizeof(file)); + EINA_LIST_FOREACH(l2, ll, p) + { + eina_strlcat(file, " ", sizeof(file)); + eina_strlcat(file, p, sizeof(file)); + } + } + icon_cache_exe = ecore_exe_run(file, NULL); + ecore_exe_run_priority_set(prio); + if (!icon_cache_exe) goto error; + + return ECORE_CALLBACK_CANCEL; + +error: + if (icon_cache_exe_lock > 0) + { + close(icon_cache_exe_lock); + icon_cache_exe_lock = -1; + } + return ECORE_CALLBACK_CANCEL; +} + +static void +desktop_cache_update_free(void *data, void *ev) +{ + Efreet_Old_Cache *d; + int dangling = 0; + + d = data; + if (d && (eina_list_data_find(old_desktop_caches, d) == d)) + { + /* + * All users should now had the chance to update their pointers. + * Check whether we still have some dangling and print a warning. + * Programs might close their pointers later. + */ + if (d->hash) + { + Eina_Iterator *it; + Eina_Hash_Tuple *tuple; + + it = eina_hash_iterator_tuple_new(d->hash); + EINA_ITERATOR_FOREACH(it, tuple) + { + if (tuple->data == NON_EXISTING) continue; + WRN("%d:%s still in cache after update event!", + ((Efreet_Desktop *)tuple->data)->ref, (char *)tuple->key); + dangling++; + } + eina_iterator_free(it); + } + if (dangling != 0) + { + WRN("There are still %i desktop files with old\n" + "dangling references to desktop files. This application\n" + "has not handled the EFREET_EVENT_DESKTOP_CACHE_UPDATE\n" + "fully and released its references. Please fix the application\n" + "so it does this.", + dangling); + } + } + free(ev); +} + +static void +icon_cache_update_free(void *data, void *ev) +{ + Efreet_Old_Cache *d; + Eina_List *l; + + l = data; + EINA_LIST_FREE(l, d) + { + if (d->hash) + eina_hash_free(d->hash); + efreet_cache_close(d->ef); + free(d); + } + free(ev); +} + +static void * +hash_array_string_add(void *hash, const char *key, void *data) +{ + if (!hash) + hash = eina_hash_string_superfast_new(EINA_FREE_CB(efreet_cache_array_string_free)); + if (!hash) + return NULL; + eina_hash_add(hash, key, data); + return hash; +} diff --git a/src/lib/efreet_cache_private.h b/src/lib/efreet_cache_private.h new file mode 100644 index 0000000..e281e61 --- /dev/null +++ b/src/lib/efreet_cache_private.h @@ -0,0 +1,63 @@ +#ifndef EFREET_CACHE_PRIVATE_H +#define EFREET_CACHE_PRIVATE_H + +#define EFREET_DESKTOP_CACHE_MAJOR 1 +#define EFREET_DESKTOP_CACHE_MINOR 0 +#define EFREET_DESKTOP_UTILS_CACHE_MAJOR 1 +#define EFREET_DESKTOP_UTILS_CACHE_MINOR 0 + +#define EFREET_ICON_CACHE_MAJOR 1 +#define EFREET_ICON_CACHE_MINOR 0 + +#define EFREET_CACHE_VERSION "__efreet//version" +#define EFREET_CACHE_ICON_FALLBACK "__efreet_fallback" +#define EFREET_CACHE_ICON_EXTENSIONS "__efreet//icon_extensions" +#define EFREET_CACHE_ICON_EXTRA_DIRS "__efreet//icon_extra_dirs" +#define EFREET_CACHE_DESKTOP_DIRS "__efreet//desktop_dirs" + +EAPI const char *efreet_desktop_util_cache_file(void); +EAPI const char *efreet_desktop_cache_file(void); +EAPI const char *efreet_icon_cache_file(const char *theme); +EAPI const char *efreet_icon_theme_cache_file(void); + +EAPI Eet_Data_Descriptor *efreet_version_edd(void); +EAPI Eet_Data_Descriptor *efreet_desktop_edd(void); +EAPI Eet_Data_Descriptor *efreet_hash_array_string_edd(void); +EAPI Eet_Data_Descriptor *efreet_hash_string_edd(void); +EAPI Eet_Data_Descriptor *efreet_array_string_edd(void); +EAPI Eet_Data_Descriptor *efreet_icon_theme_edd(Eina_Bool cache); +EAPI Eet_Data_Descriptor *efreet_icon_edd(void); +EAPI Eet_Data_Descriptor *efreet_icon_fallback_edd(void); + +typedef struct _Efreet_Cache_Icon_Theme Efreet_Cache_Icon_Theme; +typedef struct _Efreet_Cache_Directory Efreet_Cache_Directory; +typedef struct _Efreet_Cache_Desktop Efreet_Cache_Desktop; + +struct _Efreet_Cache_Icon_Theme +{ + Efreet_Icon_Theme theme; + + long long last_cache_check; /**< Last time the cache was checked */ + + Eina_Hash *dirs; /**< All possible icon paths for this theme */ + + const char *path; /**< path to index.theme */ + + Eina_Bool hidden:1; /**< Should this theme be hidden from users */ + Eina_Bool valid:1; /**< Have we seen an index for this theme */ + Eina_Bool changed:1; /**< Changed since last seen */ +}; + +struct _Efreet_Cache_Directory +{ + long long modified_time; +}; + +struct _Efreet_Cache_Desktop +{ + Efreet_Desktop desktop; + + double check_time; /**< Last time we check for disk modification */ +}; + +#endif diff --git a/src/lib/efreet_desktop.c b/src/lib/efreet_desktop.c new file mode 100644 index 0000000..9293f94 --- /dev/null +++ b/src/lib/efreet_desktop.c @@ -0,0 +1,1085 @@ +#ifdef HAVE_CONFIG_H +# include +#endif + +#undef alloca +#ifdef HAVE_ALLOCA_H +# include +#elif defined __GNUC__ +# define alloca __builtin_alloca +#elif defined _AIX +# define alloca __alloca +#elif defined _MSC_VER +# include +# define alloca _alloca +#else +# include +# ifdef __cplusplus +extern "C" +# endif +void *alloca (size_t); +#endif + +#ifdef HAVE_EVIL +# include +#endif + +#include + +/* define macros and variable for using the eina logging system */ +#define EFREET_MODULE_LOG_DOM _efreet_desktop_log_dom +int _efreet_desktop_log_dom = -1; + +#include "Efreet.h" +#include "efreet_private.h" + +#define DESKTOP_VERSION "1.0" + +/** + * The current desktop environment (e.g. "Enlightenment" or "Gnome") + */ +static const char *desktop_environment = NULL; + +/** + * A list of the desktop types available + */ +static Eina_List *efreet_desktop_types = NULL; + +static Eina_Hash *change_monitors = NULL; + +EAPI int EFREET_DESKTOP_TYPE_APPLICATION = 0; +EAPI int EFREET_DESKTOP_TYPE_LINK = 0; +EAPI int EFREET_DESKTOP_TYPE_DIRECTORY = 0; + +/** + * @internal + * Information about custom types + */ +typedef struct Efreet_Desktop_Type_Info Efreet_Desktop_Type_Info; +struct Efreet_Desktop_Type_Info +{ + int id; + const char *type; + Efreet_Desktop_Type_Parse_Cb parse_func; + Efreet_Desktop_Type_Save_Cb save_func; + Efreet_Desktop_Type_Free_Cb free_func; +}; + +static int efreet_desktop_read(Efreet_Desktop *desktop); +static Efreet_Desktop_Type_Info *efreet_desktop_type_parse(const char *type_str); +static void efreet_desktop_type_info_free(Efreet_Desktop_Type_Info *info); +static void *efreet_desktop_application_fields_parse(Efreet_Desktop *desktop, + Efreet_Ini *ini); +static void efreet_desktop_application_fields_save(Efreet_Desktop *desktop, + Efreet_Ini *ini); +static void *efreet_desktop_link_fields_parse(Efreet_Desktop *desktop, + Efreet_Ini *ini); +static void efreet_desktop_link_fields_save(Efreet_Desktop *desktop, + Efreet_Ini *ini); +static int efreet_desktop_generic_fields_parse(Efreet_Desktop *desktop, + Efreet_Ini *ini); +static void efreet_desktop_generic_fields_save(Efreet_Desktop *desktop, + Efreet_Ini *ini); +static Eina_Bool efreet_desktop_x_fields_parse(const Eina_Hash *hash, + const void *key, + void *data, + void *fdata); +static Eina_Bool efreet_desktop_x_fields_save(const Eina_Hash *hash, + const void *key, + void *value, + void *fdata); +static int efreet_desktop_environment_check(Efreet_Desktop *desktop); + +static void efreet_desktop_changes_listen(void); +static void efreet_desktop_changes_listen_recursive(const char *path); +static void efreet_desktop_changes_monitor_add(const char *path); +static void efreet_desktop_changes_cb(void *data, Ecore_File_Monitor *em, + Ecore_File_Event event, const char *path); + +/** + * @internal + * @return Returns > 0 on success or 0 on failure + * @brief Initialize the Desktop parser subsystem + */ +int +efreet_desktop_init(void) +{ + _efreet_desktop_log_dom = eina_log_domain_register + ("efreet_desktop", EFREET_DEFAULT_LOG_COLOR); + if (_efreet_desktop_log_dom < 0) + { + EINA_LOG_ERR("Efreet: Could not create a log domain for efreet_desktop"); + return 0; + } + +#ifdef HAVE_EVIL + if (!evil_sockets_init()) + { + ERR("Could not initialize Winsock system"); + return 0; + } +#endif + + efreet_desktop_types = NULL; + + EFREET_DESKTOP_TYPE_APPLICATION = efreet_desktop_type_add("Application", + efreet_desktop_application_fields_parse, + efreet_desktop_application_fields_save, + NULL); + EFREET_DESKTOP_TYPE_LINK = efreet_desktop_type_add("Link", + efreet_desktop_link_fields_parse, + efreet_desktop_link_fields_save, NULL); + EFREET_DESKTOP_TYPE_DIRECTORY = efreet_desktop_type_add("Directory", NULL, + NULL, NULL); + + efreet_desktop_changes_listen(); + return 1; +} + +/** + * @internal + * @returns the number of initializations left for this system + * @brief Attempts to shut down the subsystem if nothing else is using it + */ +void +efreet_desktop_shutdown(void) +{ + Efreet_Desktop_Type_Info *info; + + IF_RELEASE(desktop_environment); + EINA_LIST_FREE(efreet_desktop_types, info) + efreet_desktop_type_info_free(info); + IF_FREE_HASH(change_monitors); +#ifdef HAVE_EVIL + evil_sockets_shutdown(); +#endif + eina_log_domain_unregister(_efreet_desktop_log_dom); + _efreet_desktop_log_dom = -1; +} + +EAPI Efreet_Desktop * +efreet_desktop_get(const char *file) +{ + Efreet_Desktop *desktop; + + EINA_SAFETY_ON_NULL_RETURN_VAL(file, NULL); + + desktop = efreet_desktop_new(file); + if (!desktop) return NULL; + + /* If we didn't find this file in the eet cache, add path to search path */ + if (!desktop->eet) + { + /* Check whether the desktop type is a system type, + * and therefor known by the cache builder */ + Efreet_Desktop_Type_Info *info; + + info = eina_list_nth(efreet_desktop_types, desktop->type); + if (info && ( + info->id == EFREET_DESKTOP_TYPE_APPLICATION || + info->id == EFREET_DESKTOP_TYPE_LINK || + info->id == EFREET_DESKTOP_TYPE_DIRECTORY + )) + efreet_cache_desktop_add(desktop); + } + + return desktop; +} + +EAPI int +efreet_desktop_ref(Efreet_Desktop *desktop) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(desktop, 0); + desktop->ref++; + return desktop->ref; +} + +EAPI Efreet_Desktop * +efreet_desktop_empty_new(const char *file) +{ + Efreet_Desktop *desktop; + + EINA_SAFETY_ON_NULL_RETURN_VAL(file, NULL); + + desktop = NEW(Efreet_Desktop, 1); + if (!desktop) return NULL; + + desktop->orig_path = strdup(file); + desktop->load_time = ecore_file_mod_time(file); + + desktop->ref = 1; + + return desktop; +} + +EAPI Efreet_Desktop * +efreet_desktop_new(const char *file) +{ + Efreet_Desktop *desktop = NULL; + + EINA_SAFETY_ON_NULL_RETURN_VAL(file, NULL); + + desktop = efreet_cache_desktop_find(file); + if (desktop) + { + desktop->ref++; + if (!efreet_desktop_environment_check(desktop)) + { + efreet_desktop_free(desktop); + return NULL; + } + return desktop; + efreet_desktop_free(desktop); + } + return efreet_desktop_uncached_new(file); +} + +EAPI Efreet_Desktop * +efreet_desktop_uncached_new(const char *file) +{ + Efreet_Desktop *desktop = NULL; + char rp[PATH_MAX]; + + EINA_SAFETY_ON_NULL_RETURN_VAL(file, NULL); + + if (!realpath(file, rp)) return NULL; + if (!ecore_file_exists(rp)) return NULL; + + desktop = NEW(Efreet_Desktop, 1); + if (!desktop) return NULL; + desktop->orig_path = strdup(rp); + desktop->ref = 1; + if (!efreet_desktop_read(desktop)) + { + efreet_desktop_free(desktop); + return NULL; + } + + return desktop; +} + +EAPI int +efreet_desktop_save(Efreet_Desktop *desktop) +{ + Efreet_Desktop_Type_Info *info; + Efreet_Ini *ini; + int ok = 1; + + EINA_SAFETY_ON_NULL_RETURN_VAL(desktop, 0); + + ini = efreet_ini_new(NULL); + if (!ini) return 0; + efreet_ini_section_add(ini, "Desktop Entry"); + efreet_ini_section_set(ini, "Desktop Entry"); + + info = eina_list_nth(efreet_desktop_types, desktop->type); + if (info) + { + efreet_ini_string_set(ini, "Type", info->type); + if (info->save_func) info->save_func(desktop, ini); + } + else + ok = 0; + + if (ok) + { + char *val; + + if (desktop->only_show_in) + { + val = efreet_desktop_string_list_join(desktop->only_show_in); + if (val) + { + efreet_ini_string_set(ini, "OnlyShowIn", val); + FREE(val); + } + } + if (desktop->not_show_in) + { + val = efreet_desktop_string_list_join(desktop->not_show_in); + if (val) + { + efreet_ini_string_set(ini, "NotShowIn", val); + FREE(val); + } + } + efreet_desktop_generic_fields_save(desktop, ini); + /* When we save the file, it should be updated to the + * latest version that we support! */ + efreet_ini_string_set(ini, "Version", DESKTOP_VERSION); + + if (!efreet_ini_save(ini, desktop->orig_path)) ok = 0; + } + efreet_ini_free(ini); + return ok; +} + +EAPI int +efreet_desktop_save_as(Efreet_Desktop *desktop, const char *file) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(desktop, 0); + EINA_SAFETY_ON_NULL_RETURN_VAL(file, 0); + + /* If we save data from eet as new, we will be in trouble */ + if (desktop->eet) return 0; + + IF_FREE(desktop->orig_path); + desktop->orig_path = strdup(file); + return efreet_desktop_save(desktop); +} + +EAPI void +efreet_desktop_free(Efreet_Desktop *desktop) +{ + if (!desktop) return; + + desktop->ref--; + if (desktop->ref > 0) return; + + if (desktop->eet) + { + efreet_cache_desktop_free(desktop); + } + else + { + IF_FREE(desktop->orig_path); + + IF_FREE(desktop->version); + IF_FREE(desktop->name); + IF_FREE(desktop->generic_name); + IF_FREE(desktop->comment); + IF_FREE(desktop->icon); + IF_FREE(desktop->url); + + IF_FREE(desktop->try_exec); + IF_FREE(desktop->exec); + IF_FREE(desktop->path); + IF_FREE(desktop->startup_wm_class); + + IF_FREE_LIST(desktop->only_show_in, eina_stringshare_del); + IF_FREE_LIST(desktop->not_show_in, eina_stringshare_del); + + IF_FREE_LIST(desktop->categories, eina_stringshare_del); + IF_FREE_LIST(desktop->mime_types, eina_stringshare_del); + + IF_FREE_HASH(desktop->x); + + if (desktop->type_data) + { + Efreet_Desktop_Type_Info *info; + info = eina_list_nth(efreet_desktop_types, desktop->type); + if (info->free_func) + info->free_func(desktop->type_data); + } + free(desktop); + } +} + +EAPI void +efreet_desktop_environment_set(const char *environment) +{ + if (desktop_environment) eina_stringshare_del(desktop_environment); + if (environment) desktop_environment = eina_stringshare_add(environment); + else desktop_environment = NULL; +} + +EAPI const char * +efreet_desktop_environment_get(void) +{ + return desktop_environment; +} + +EAPI unsigned int +efreet_desktop_category_count_get(Efreet_Desktop *desktop) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(desktop, 0); + return eina_list_count(desktop->categories); +} + +EAPI void +efreet_desktop_category_add(Efreet_Desktop *desktop, const char *category) +{ + EINA_SAFETY_ON_NULL_RETURN(desktop); + EINA_SAFETY_ON_NULL_RETURN(category); + + if (eina_list_search_unsorted(desktop->categories, + EINA_COMPARE_CB(strcmp), category)) return; + + desktop->categories = eina_list_append(desktop->categories, + (void *)eina_stringshare_add(category)); +} + +EAPI int +efreet_desktop_category_del(Efreet_Desktop *desktop, const char *category) +{ + char *found = NULL; + + EINA_SAFETY_ON_NULL_RETURN_VAL(desktop, 0); + + if ((found = eina_list_search_unsorted(desktop->categories, + EINA_COMPARE_CB(strcmp), category))) + { + eina_stringshare_del(found); + desktop->categories = eina_list_remove(desktop->categories, found); + + return 1; + } + + return 0; +} + +EAPI int +efreet_desktop_type_add(const char *type, Efreet_Desktop_Type_Parse_Cb parse_func, + Efreet_Desktop_Type_Save_Cb save_func, + Efreet_Desktop_Type_Free_Cb free_func) +{ + int id; + Efreet_Desktop_Type_Info *info; + + info = NEW(Efreet_Desktop_Type_Info, 1); + if (!info) return 0; + + id = eina_list_count(efreet_desktop_types); + + info->id = id; + info->type = eina_stringshare_add(type); + info->parse_func = parse_func; + info->save_func = save_func; + info->free_func = free_func; + + efreet_desktop_types = eina_list_append(efreet_desktop_types, info); + + return id; +} + +EAPI int +efreet_desktop_type_alias(int from_type, const char *alias) +{ + Efreet_Desktop_Type_Info *info; + info = eina_list_nth(efreet_desktop_types, from_type); + if (!info) return -1; + + return efreet_desktop_type_add(alias, info->parse_func, info->save_func, info->free_func); +} + +EAPI Eina_Bool +efreet_desktop_x_field_set(Efreet_Desktop *desktop, const char *key, const char *data) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(desktop, EINA_FALSE); + EINA_SAFETY_ON_TRUE_RETURN_VAL(strncmp(key, "X-", 2), EINA_FALSE); + + if (!desktop->x) + desktop->x = eina_hash_string_superfast_new(EINA_FREE_CB(eina_stringshare_del)); + + eina_hash_del_by_key(desktop->x, key); + eina_hash_add(desktop->x, key, eina_stringshare_add(data)); + + return EINA_TRUE; +} + +EAPI const char * +efreet_desktop_x_field_get(Efreet_Desktop *desktop, const char *key) +{ + const char *ret; + + EINA_SAFETY_ON_NULL_RETURN_VAL(desktop, NULL); + EINA_SAFETY_ON_NULL_RETURN_VAL(desktop->x, NULL); + EINA_SAFETY_ON_TRUE_RETURN_VAL(strncmp(key, "X-", 2), NULL); + + ret = eina_hash_find(desktop->x, key); + if (!ret) + return NULL; + + return eina_stringshare_add(ret); +} + +EAPI Eina_Bool +efreet_desktop_x_field_del(Efreet_Desktop *desktop, const char *key) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(desktop, EINA_FALSE); + EINA_SAFETY_ON_TRUE_RETURN_VAL(strncmp(key, "X-", 2), EINA_FALSE); + EINA_SAFETY_ON_NULL_RETURN_VAL(desktop->x, EINA_FALSE); + + return eina_hash_del_by_key(desktop->x, key); +} + +EAPI void * +efreet_desktop_type_data_get(Efreet_Desktop *desktop) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(desktop, NULL); + return desktop->type_data; +} + +EAPI Eina_List * +efreet_desktop_string_list_parse(const char *string) +{ + Eina_List *list = NULL; + char *tmp; + char *s, *p; + size_t len; + + EINA_SAFETY_ON_NULL_RETURN_VAL(string, NULL); + + len = strlen(string) + 1; + tmp = alloca(len); + memcpy(tmp, string, len); + s = tmp; + + while ((p = strchr(s, ';'))) + { + if (p > tmp && *(p-1) == '\\') continue; + *p = '\0'; + list = eina_list_append(list, (void *)eina_stringshare_add(s)); + s = p + 1; + } + /* If this is true, the .desktop file does not follow the standard */ + if (*s) + { +#ifdef STRICT_SPEC + WRN("[Efreet]: Found a string list without ';' " + "at the end: %s", string); +#endif + list = eina_list_append(list, (void *)eina_stringshare_add(s)); + } + + return list; +} + +EAPI char * +efreet_desktop_string_list_join(Eina_List *list) +{ + Eina_List *l; + const char *elem; + char *string; + size_t size, pos, len; + + if (!list) return strdup(""); + + size = 1024; + string = malloc(size); + if (!string) return NULL; + pos = 0; + + EINA_LIST_FOREACH(list, l, elem) + { + len = strlen(elem); + /* +1 for ';' */ + if ((len + pos + 1) >= size) + { + char *tmp; + size = len + pos + 1024; + tmp = realloc(string, size); + if (!tmp) + { + free(string); + return NULL; + } + string = tmp; + } + strcpy(string + pos, elem); + pos += len; + strcpy(string + pos, ";"); + pos += 1; + } + return string; +} + +/** + * @internal + * @param desktop The desktop to fill + * @return Returns 1 on success, 0 on failure + * @brief initialize an Efreet_Desktop from the contents of @a file + */ +static int +efreet_desktop_read(Efreet_Desktop *desktop) +{ + Efreet_Ini *ini; + int error = 0; + int ok; + + ini = efreet_ini_new(desktop->orig_path); + if (!ini) return 0; + if (!ini->data) + { + efreet_ini_free(ini); + return 0; + } + + ok = efreet_ini_section_set(ini, "Desktop Entry"); + if (!ok) ok = efreet_ini_section_set(ini, "KDE Desktop Entry"); + if (!ok) + { + ERR("efreet_desktop_new error: no Desktop Entry section"); + error = 1; + } + + if (!error) + { + Efreet_Desktop_Type_Info *info; + + info = efreet_desktop_type_parse(efreet_ini_string_get(ini, "Type")); + if (info) + { + const char *val; + + desktop->type = info->id; + val = efreet_ini_string_get(ini, "Version"); + if (val) desktop->version = strdup(val); + + if (info->parse_func) + desktop->type_data = info->parse_func(desktop, ini); + } + else + error = 1; + } + + if (!error && !efreet_desktop_generic_fields_parse(desktop, ini)) error = 1; + if (!error && !efreet_desktop_environment_check(desktop)) error = 1; + if (!error) + eina_hash_foreach(ini->section, efreet_desktop_x_fields_parse, desktop); + + efreet_ini_free(ini); + + desktop->load_time = ecore_file_mod_time(desktop->orig_path); + + if (error) return 0; + + return 1; +} + +/** + * @internal + * @param type_str the type as a string + * @return the parsed type + * @brief parse the type string into an Efreet_Desktop_Type + */ +static Efreet_Desktop_Type_Info * +efreet_desktop_type_parse(const char *type_str) +{ + Efreet_Desktop_Type_Info *info; + Eina_List *l; + + if (!type_str) return NULL; + + EINA_LIST_FOREACH(efreet_desktop_types, l, info) + { + if (!strcmp(info->type, type_str)) + return info; + } + + return NULL; +} + +/** + * @internal + * @brief Free an Efreet Desktop_Type_Info struct + */ +static void +efreet_desktop_type_info_free(Efreet_Desktop_Type_Info *info) +{ + if (!info) return; + IF_RELEASE(info->type); + free(info); +} + +/** + * @internal + * @param desktop the Efreet_Desktop to store parsed fields in + * @param ini the Efreet_Ini to parse fields from + * @return No value + * @brief Parse application specific desktop fields + */ +static void * +efreet_desktop_application_fields_parse(Efreet_Desktop *desktop, Efreet_Ini *ini) +{ + const char *val; + + val = efreet_ini_string_get(ini, "TryExec"); + if (val) desktop->try_exec = strdup(val); + + val = efreet_ini_string_get(ini, "Exec"); + if (val) desktop->exec = strdup(val); + + val = efreet_ini_string_get(ini, "Path"); + if (val) desktop->path = strdup(val); + + val = efreet_ini_string_get(ini, "StartupWMClass"); + if (val) desktop->startup_wm_class = strdup(val); + + val = efreet_ini_string_get(ini, "Categories"); + if (val) + desktop->categories = efreet_desktop_string_list_parse(val); + val = efreet_ini_string_get(ini, "MimeType"); + if (val) desktop->mime_types = efreet_desktop_string_list_parse(val); + + desktop->terminal = efreet_ini_boolean_get(ini, "Terminal"); + desktop->startup_notify = efreet_ini_boolean_get(ini, "StartupNotify"); + + return NULL; +} + +/** + * @internal + * @param desktop the Efreet_Desktop to save fields from + * @param ini the Efreet_Ini to save fields to + * @return Returns no value + * @brief Save application specific desktop fields + */ +static void +efreet_desktop_application_fields_save(Efreet_Desktop *desktop, Efreet_Ini *ini) +{ + char *val; + + if (desktop->try_exec) + efreet_ini_string_set(ini, "TryExec", desktop->try_exec); + + if (desktop->exec) + efreet_ini_string_set(ini, "Exec", desktop->exec); + + if (desktop->path) + efreet_ini_string_set(ini, "Path", desktop->path); + + if (desktop->startup_wm_class) + efreet_ini_string_set(ini, "StartupWMClass", desktop->startup_wm_class); + + if (desktop->categories) + { + val = efreet_desktop_string_list_join(desktop->categories); + if (val) + { + efreet_ini_string_set(ini, "Categories", val); + FREE(val); + } + } + + if (desktop->mime_types) + { + val = efreet_desktop_string_list_join(desktop->mime_types); + if (val) + { + efreet_ini_string_set(ini, "MimeType", val); + FREE(val); + } + } + + efreet_ini_boolean_set(ini, "Terminal", desktop->terminal); + efreet_ini_boolean_set(ini, "StartupNotify", desktop->startup_notify); +} + +/** + * @internal + * @param desktop the Efreet_Desktop to store parsed fields in + * @param ini the Efreet_Ini to parse fields from + * @return Returns no value + * @brief Parse link specific desktop fields + */ +static void * +efreet_desktop_link_fields_parse(Efreet_Desktop *desktop, Efreet_Ini *ini) +{ + const char *val; + + val = efreet_ini_string_get(ini, "URL"); + if (val) desktop->url = strdup(val); + return NULL; +} + +/** + * @internal + * @param desktop the Efreet_Desktop to save fields from + * @param ini the Efreet_Ini to save fields in + * @return Returns no value + * @brief Save link specific desktop fields + */ +static void +efreet_desktop_link_fields_save(Efreet_Desktop *desktop, Efreet_Ini *ini) +{ + if (desktop->url) efreet_ini_string_set(ini, "URL", desktop->url); +} + +/** + * @internal + * @param desktop the Efreet_Desktop to store parsed fields in + * @param ini the Efreet_Ini to parse fields from + * @return 1 if parsed successfully, 0 otherwise + * @brief Parse desktop fields that all types can include + */ +static int +efreet_desktop_generic_fields_parse(Efreet_Desktop *desktop, Efreet_Ini *ini) +{ + const char *val; + const char *not_show_in = NULL, *only_show_in = NULL; + + val = efreet_ini_localestring_get(ini, "Name"); +#ifndef STRICT_SPEC + if (!val) val = efreet_ini_localestring_get(ini, "_Name"); +#endif + if (val) desktop->name = strdup(val); + else + { + ERR("efreet_desktop_generic_fields_parse error: no Name or _Name fields"); + return 0; + } + + val = efreet_ini_localestring_get(ini, "GenericName"); + if (val) desktop->generic_name = strdup(val); + + val = efreet_ini_localestring_get(ini, "Comment"); +#ifndef STRICT_SPEC + if (!val) val = efreet_ini_localestring_get(ini, "_Comment"); +#endif + if (val) desktop->comment = strdup(val); + + val = efreet_ini_localestring_get(ini, "Icon"); + if (val) desktop->icon = strdup(val); + + desktop->no_display = efreet_ini_boolean_get(ini, "NoDisplay"); + desktop->hidden = efreet_ini_boolean_get(ini, "Hidden"); + + only_show_in = efreet_ini_string_get(ini, "OnlyShowIn"); + not_show_in = efreet_ini_string_get(ini, "NotShowIn"); + if (only_show_in && not_show_in) + WRN("Both OnlyShowIn and NotShowIn in %s, preferring OnlyShowIn", desktop->orig_path); + if (only_show_in) desktop->only_show_in = efreet_desktop_string_list_parse(only_show_in); + else if (not_show_in) desktop->not_show_in = efreet_desktop_string_list_parse(not_show_in); + + return 1; +} + +/** + * @internal + * @param desktop the Efreet_Desktop to save fields from + * @param ini the Efreet_Ini to save fields to + * @return Returns nothing + * @brief Save desktop fields that all types can include + */ +static void +efreet_desktop_generic_fields_save(Efreet_Desktop *desktop, Efreet_Ini *ini) +{ + const char *val; + + if (desktop->name) + { + efreet_ini_localestring_set(ini, "Name", desktop->name); + val = efreet_ini_string_get(ini, "Name"); + if (!val) + efreet_ini_string_set(ini, "Name", desktop->name); + } + if (desktop->generic_name) + { + efreet_ini_localestring_set(ini, "GenericName", desktop->generic_name); + val = efreet_ini_string_get(ini, "GenericName"); + if (!val) + efreet_ini_string_set(ini, "GenericName", desktop->generic_name); + } + if (desktop->comment) + { + efreet_ini_localestring_set(ini, "Comment", desktop->comment); + val = efreet_ini_string_get(ini, "Comment"); + if (!val) + efreet_ini_string_set(ini, "Comment", desktop->comment); + } + if (desktop->icon) + { + efreet_ini_localestring_set(ini, "Icon", desktop->icon); + val = efreet_ini_string_get(ini, "Icon"); + if (!val) + efreet_ini_string_set(ini, "Icon", desktop->icon); + } + + efreet_ini_boolean_set(ini, "NoDisplay", desktop->no_display); + efreet_ini_boolean_set(ini, "Hidden", desktop->hidden); + + if (desktop->x) eina_hash_foreach(desktop->x, efreet_desktop_x_fields_save, + ini); +} + +/** + * @internal + * @param node The node to work with + * @param desktop The desktop file to work with + * @return Returns always true, to be used in eina_hash_foreach() + * @brief Parses out an X- key from @a node and stores in @a desktop + */ +static Eina_Bool +efreet_desktop_x_fields_parse(const Eina_Hash *hash __UNUSED__, const void *key, void *value, void *fdata) +{ + Efreet_Desktop * desktop = fdata; + + if (!desktop) return EINA_TRUE; + if (strncmp(key, "X-", 2)) return EINA_TRUE; + + if (!desktop->x) + desktop->x = eina_hash_string_superfast_new(EINA_FREE_CB(eina_stringshare_del)); + eina_hash_del_by_key(desktop->x, key); + eina_hash_add(desktop->x, key, (void *)eina_stringshare_add(value)); + + return EINA_TRUE; +} + +/** + * @internal + * @param node The node to work with + * @param ini The ini file to work with + * @return Returns no value + * @brief Stores an X- key from @a node and stores in @a ini + */ +static Eina_Bool +efreet_desktop_x_fields_save(const Eina_Hash *hash __UNUSED__, const void *key, void *value, void *fdata) +{ + Efreet_Ini *ini = fdata; + efreet_ini_string_set(ini, key, value); + + return EINA_TRUE; +} + + +/** + * @internal + * @param ini The Efreet_Ini to parse values from + * @return 1 if desktop should be included in current environement, 0 otherwise + * @brief Determines if a desktop should be included in the current environment, + * based on the values of the OnlyShowIn and NotShowIn fields + */ +static int +efreet_desktop_environment_check(Efreet_Desktop *desktop) +{ + Eina_List *list; + int found = 0; + char *val; + + if (!desktop_environment) + { + //if (desktop->only_show_in) return 0; + return 1; + } + + if (desktop->only_show_in) + { + EINA_LIST_FOREACH(desktop->only_show_in, list, val) + { + if (!strcmp(val, desktop_environment)) + { + found = 1; + break; + } + } + return found; + } + + + if (desktop->not_show_in) + { + EINA_LIST_FOREACH(desktop->not_show_in, list, val) + { + if (!strcmp(val, desktop_environment)) + { + found = 1; + break; + } + } + return !found; + } + + return 1; +} + +static void +efreet_desktop_changes_listen(void) +{ + Efreet_Cache_Array_String *arr; + Eina_List *dirs; + const char *path; + + if (!efreet_cache_update) return; + + change_monitors = eina_hash_string_superfast_new(EINA_FREE_CB(ecore_file_monitor_del)); + if (!change_monitors) return; + + dirs = efreet_default_dirs_get(efreet_data_home_get(), + efreet_data_dirs_get(), "applications"); + + EINA_LIST_FREE(dirs, path) + { + if (ecore_file_is_dir(path)) + efreet_desktop_changes_listen_recursive(path); + eina_stringshare_del(path); + } + + arr = efreet_cache_desktop_dirs(); + if (arr) + { + unsigned int i; + + for (i = 0; i < arr->array_count; i++) + efreet_desktop_changes_monitor_add(arr->array[i]); + efreet_cache_array_string_free(arr); + } +} + +static void +efreet_desktop_changes_listen_recursive(const char *path) +{ + Eina_Iterator *it; + Eina_File_Direct_Info *info; + + efreet_desktop_changes_monitor_add(path); + + it = eina_file_direct_ls(path); + if (!it) return; + EINA_ITERATOR_FOREACH(it, info) + { + if (ecore_file_is_dir(info->path)) efreet_desktop_changes_listen_recursive(info->path); + } + eina_iterator_free(it); +} + +static void +efreet_desktop_changes_monitor_add(const char *path) +{ + char rp[PATH_MAX]; + + if (!realpath(path, rp)) return; + if (eina_hash_find(change_monitors, rp)) return; + eina_hash_add(change_monitors, rp, + ecore_file_monitor_add(rp, + efreet_desktop_changes_cb, + NULL)); +} + +static void +efreet_desktop_changes_cb(void *data __UNUSED__, Ecore_File_Monitor *em __UNUSED__, + Ecore_File_Event event, const char *path) +{ + const char *ext; + + /* TODO: If we get a stale symlink, we need to rerun cache creation */ + /* TODO: Check for desktop*.cache, as this will be created when app is installed */ + /* TODO: Do efreet_cache_icon_update() when app is installed, as it has the same + * symlink problem */ + switch (event) + { + case ECORE_FILE_EVENT_NONE: + /* noop */ + break; + + case ECORE_FILE_EVENT_CREATED_FILE: + case ECORE_FILE_EVENT_DELETED_FILE: + case ECORE_FILE_EVENT_MODIFIED: + case ECORE_FILE_EVENT_CLOSED: + ext = strrchr(path, '.'); + if (ext && (!strcmp(ext, ".desktop") || !strcmp(ext, ".directory"))) + efreet_cache_desktop_update(); + break; + + case ECORE_FILE_EVENT_DELETED_SELF: + case ECORE_FILE_EVENT_DELETED_DIRECTORY: + eina_hash_del_by_key(change_monitors, path); + efreet_cache_desktop_update(); + break; + + case ECORE_FILE_EVENT_CREATED_DIRECTORY: + efreet_desktop_changes_monitor_add(path); + efreet_cache_desktop_update(); + break; + } +} diff --git a/src/lib/efreet_desktop.h b/src/lib/efreet_desktop.h new file mode 100644 index 0000000..f7dfe51 --- /dev/null +++ b/src/lib/efreet_desktop.h @@ -0,0 +1,370 @@ +#ifndef EFREET_DESKTOP_H +#define EFREET_DESKTOP_H + +/** + * @file efreet_desktop.h + * @brief Contains the structures and methods used to support the + * FDO desktop entry specificiation. + * @addtogroup Efreet_Desktop Efreet_Desktop: The FDO Desktop Entry + * Specification functions and structures + * + * @{ + */ + + +/** + * @param desktop the desktop entry + * @param files an eina list of file names to execute, as either absolute paths, + * relative paths, or uris + * @param func a callback to call for each prepared command line + * @param data user data passed to the callback + * @return Returns the return value of @p func on success or NULL on failure + * @brief Get a command to use to execute a desktop entry. + */ +EAPI extern int EFREET_DESKTOP_TYPE_APPLICATION; +EAPI extern int EFREET_DESKTOP_TYPE_LINK; +EAPI extern int EFREET_DESKTOP_TYPE_DIRECTORY; + +/** + * Event id for cache update. All users of efreet_desktop_get must listen to + * this event and refetch. The old eet cache will be closed and mem will + * be invalidated. + */ +EAPI extern int EFREET_EVENT_DESKTOP_CACHE_UPDATE; +/** + * Event id for cache build complete. + * @since 1.1.0 + */ +EAPI extern int EFREET_EVENT_DESKTOP_CACHE_BUILD; + +/** + * Efreet_Desktop + */ +typedef struct _Efreet_Desktop Efreet_Desktop; + +/** + * A callback used with efreet_desktop_command_get() + */ +typedef void *(*Efreet_Desktop_Command_Cb) (void *data, Efreet_Desktop *desktop, + char *command, int remaining); + +/** + * A callback used to get download progress of remote uris + */ +typedef int (*Efreet_Desktop_Progress_Cb) (void *data, Efreet_Desktop *desktop, + char *uri, long int total, long int current); + +/** + * A callback used to parse data for custom types + */ +typedef void *(*Efreet_Desktop_Type_Parse_Cb) (Efreet_Desktop *desktop, Efreet_Ini *ini); + +/** + * A callback used to save data for custom types + */ +typedef void (*Efreet_Desktop_Type_Save_Cb) (Efreet_Desktop *desktop, Efreet_Ini *ini); + +/** + * A callback used to free data for custom types + */ +typedef void *(*Efreet_Desktop_Type_Free_Cb) (void *data); + +/** + * Efreet_Desktop + * @brief a parsed representation of a .desktop file + */ +struct _Efreet_Desktop +{ + int type; /**< type of desktop file */ + + int ref; /**< reference count - internal */ + + char *version; /**< version of spec file conforms to */ + + char *orig_path; /**< original path to .desktop file */ + long long load_time; /**< modified time of .desktop on disk */ + + char *name; /**< Specific name of the application */ + char *generic_name; /**< Generic name of the application */ + char *comment; /**< Tooltip for the entry */ + char *icon; /**< Icon to display in file manager, menus, etc */ + char *try_exec; /**< Binary to determine if app is installed */ + char *exec; /**< Program to execute */ + char *path; /**< Working directory to run app in */ + char *startup_wm_class; /**< If specified will map at least one window with + the given string as it's WM class or WM name */ + char *url; /**< URL to access if type is EFREET_TYPE_LINK */ + + Eina_List *only_show_in; /**< list of environments that should + display the icon */ + Eina_List *not_show_in; /**< list of environments that shoudn't + display the icon */ + Eina_List *categories; /**< Categories in which item should be shown */ + Eina_List *mime_types; /**< The mime types supppored by this app */ + + unsigned char no_display; /**< Don't display this application in menus */ + unsigned char hidden; /**< User delete the item */ + unsigned char terminal; /**< Does the program run in a terminal */ + unsigned char startup_notify; /**< The starup notify settings of the app */ + unsigned char eet:1; /**< The desktop file is in eet cache */ + + Eina_Hash *x; /**< Keep track of all user extensions, keys that begin with X- */ + void *type_data; /**< Type specific data for custom types */ +}; + + +/** + * @param file The file to get the Efreet_Desktop from + * @return Returns a reference to a cached Efreet_Desktop on success, NULL + * on failure + * @brief Gets a reference to an Efreet_Desktop structure representing the + * contents of @a file or NULL if @a file is not a valid .desktop file. + * + * By using efreet_desktop_get the Efreet_Desktop will be saved in an internal + * cache for quicker loading. + * + * Users of this command should listen to EFREET_EVENT_DESKTOP_CACHE_UPDATE + * event, if the application is to keep the reference. When the event fires + * the Efreet_Desktop struct should be invalidated and reloaded from a new + * cache file. + */ +EAPI Efreet_Desktop *efreet_desktop_get(const char *file); + +/** + * @param desktop The Efreet_Desktop to ref + * @return Returns the new reference count + * @brief Increases reference count on desktop + */ +EAPI int efreet_desktop_ref(Efreet_Desktop *desktop); + +/** + * @param file The file to create the Efreet_Desktop from + * @return Returns a new empty_Efreet_Desktop on success, NULL on failure + * @brief Creates a new empty Efreet_Desktop structure or NULL on failure + */ +EAPI Efreet_Desktop *efreet_desktop_empty_new(const char *file); + +/** + * @param file The file to get the Efreet_Desktop from + * @return Returns a reference to a cached Efreet_Desktop on success, NULL + * on failure + * @brief Gets a reference to an Efreet_Desktop structure representing the + * contents of @a file or NULL if @a file is not a valid .desktop file. + * + * Users of this command should listen to EFREET_EVENT_DESKTOP_CACHE_UPDATE + * event, if the application is to keep the reference. When the event fires + * the Efreet_Desktop struct should be invalidated and reloaded from a new + * cache file. + */ +EAPI Efreet_Desktop *efreet_desktop_new(const char *file); + +/** + * @param file The file to create the Efreet_Desktop from + * @return Returns a new Efreet_Desktop on success, NULL on failure + * @brief Creates a new Efreet_Desktop structure initialized from the + * contents of @a file or NULL on failure + * + * By using efreet_desktop_uncached_new the Efreet_Desktop structure will be + * read from disk, and not from any cache. + * + * Data in the structure is allocated with strdup, so use free and strdup to + * change values. + */ +EAPI Efreet_Desktop *efreet_desktop_uncached_new(const char *file); + +/** + * @param desktop The Efreet_Desktop to work with + * @return Returns no value + * @brief Frees the Efreet_Desktop structure and all of it's data + */ +EAPI void efreet_desktop_free(Efreet_Desktop *desktop); + +/** + * @def efreet_desktop_unref(desktop) + * Alias for efreet_desktop_free(desktop) + */ +#define efreet_desktop_unref(desktop) efreet_desktop_free((desktop)) + + +/** + * @param desktop The desktop file to save + * @return Returns 1 on success or 0 on failure + * @brief Saves any changes made to @a desktop back to the file on the + * filesystem + */ +EAPI int efreet_desktop_save(Efreet_Desktop *desktop); + +/** + * @param desktop The desktop file to save + * @param file The filename to save as + * @return Returns 1 on success or 0 on failure + * @brief Saves @a desktop to @a file + * + * Please use efreet_desktop_uncached_new() on an existing file + * before using efreet_desktop_save_as() + */ +EAPI int efreet_desktop_save_as(Efreet_Desktop *desktop, + const char *file); + + +/** + * @param desktop The desktop file to work with + * @param files The files to be substituted into the exec line + * @param data The data pointer to pass + * @return Returns the Ecore_Exce for @a desktop + * @brief Parses the @a desktop exec line and returns an Ecore_Exe. + */ +EAPI void efreet_desktop_exec(Efreet_Desktop *desktop, + Eina_List *files, void *data); + + +/** + * @param environment the environment name + * @brief sets the global desktop environment name + */ +EAPI void efreet_desktop_environment_set(const char *environment); + +/** + * @return environment the environment name + * @brief sets the global desktop environment name + */ +EAPI const char *efreet_desktop_environment_get(void); + +/** + * @param desktop the desktop entry + * @param files an eina list of file names to execute, as either absolute paths, + * relative paths, or uris + * @param cb_command a callback to call for each prepared command line + * @param cb_prog a callback to get progress for the downloads + * @param data user data passed to the callback + * @return Returns 1 on success or 0 on failure + * @brief Get a command to use to execute a desktop entry, and receive progress + * updates for downloading of remote URI's passed in. + */ +EAPI void *efreet_desktop_command_progress_get(Efreet_Desktop *desktop, + Eina_List *files, + Efreet_Desktop_Command_Cb cb_command, + Efreet_Desktop_Progress_Cb cb_prog, + void *data); +EAPI void *efreet_desktop_command_get(Efreet_Desktop *desktop, + Eina_List *files, + Efreet_Desktop_Command_Cb func, + void *data); + +/** + * @param desktop the desktop entry + * @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) + * @return Returns an eina list of exec strings + * @brief Get the command to use to execute a desktop entry + * + * The returned list and each of its elements must be freed. + */ +EAPI Eina_List * efreet_desktop_command_local_get(Efreet_Desktop *desktop, + Eina_List *files); + + +/** + * @param desktop The desktop to work with + * @return Returns the number of categories assigned to this desktop + * @brief Retrieves the number of categories the given @a desktop belongs + * too + */ +EAPI unsigned int efreet_desktop_category_count_get(Efreet_Desktop *desktop); + +/** + * @param desktop the desktop + * @param category the category name + * @brief add a category to a desktop + */ +EAPI void efreet_desktop_category_add(Efreet_Desktop *desktop, + const char *category); + +/** + * @param desktop the desktop + * @param category the category name + * @brief removes a category from a desktop + * @return 1 if the desktop had his category listed, 0 otherwise + */ +EAPI int efreet_desktop_category_del(Efreet_Desktop *desktop, + const char *category); + + +/** + * @param type The type to add to the list of matching types + * @param parse_func a function to parse out custom fields + * @param save_func a function to save data returned from @a parse_func + * @param free_func a function to free data returned from @a parse_func + * @return Returns the id of the new type + * @brief Adds the given type to the list of types in the system + */ +EAPI int efreet_desktop_type_add(const char *type, + Efreet_Desktop_Type_Parse_Cb parse_func, + Efreet_Desktop_Type_Save_Cb save_func, + Efreet_Desktop_Type_Free_Cb free_func); + +/** + * @brief Add an alias for an existing desktop type. + * @param from_type the type to alias (e.g. EFREE_DESKTOP_TYPE_APPLICATION) + * @param alias the alias + * @return the new type id, or -1 if @p from_type was not valid + * + * This allows applications to add non-standard types that behave exactly as standard types. + */ +EAPI int efreet_desktop_type_alias (int from_type, + const char *alias); + +/** + * @brief get type specific data for custom desktop types + * @param desktop the desktop + * @return type specific data, or NULL if there is none + */ +EAPI void *efreet_desktop_type_data_get(Efreet_Desktop *desktop); + + +/** + * @param string the raw string list + * @return an Eina_List of ecore string's + * @brief Parse ';' separate list of strings according to the desktop spec + */ +EAPI Eina_List *efreet_desktop_string_list_parse(const char *string); + +/** + * @param list Eina_List with strings + * @return a raw string list + * @brief Create a ';' separate list of strings according to the desktop spec + */ +EAPI char *efreet_desktop_string_list_join(Eina_List *list); + + +/** + * @brief Set the value for a X- field (Non spec) in the structure + * @param desktop the desktop + * @param key the key name to set + * @param data the value to set + * @return EINA_TRUE on success + * + * The key has to start with "X-" + */ +EAPI Eina_Bool efreet_desktop_x_field_set(Efreet_Desktop *desktop, const char *key, const char *data); + +/** + * @brief Get the value for a X- field (Non spec) in the structure + * @param desktop the desktop + * @param key the key + * @return The value referenced by the key, or NULL if the key does not exist + */ +EAPI const char * efreet_desktop_x_field_get(Efreet_Desktop *desktop, const char *key); + +/** + * @brief Delete the key and value for a X- field (Non spec) in the structure + * @param desktop the desktop + * @param key the key + * @return EINA_TRUE if the key existed + */ +EAPI Eina_Bool efreet_desktop_x_field_del(Efreet_Desktop *desktop, const char *key); + +/** + * @} + */ + +#endif diff --git a/src/lib/efreet_desktop_command.c b/src/lib/efreet_desktop_command.c new file mode 100644 index 0000000..bf97f8f --- /dev/null +++ b/src/lib/efreet_desktop_command.c @@ -0,0 +1,919 @@ +#ifdef HAVE_CONFIG_H +# include +#endif + +#undef alloca +#ifdef HAVE_ALLOCA_H +# include +#elif defined __GNUC__ +# define alloca __builtin_alloca +#elif defined _AIX +# define alloca __alloca +#elif defined _MSC_VER +# include +# define alloca _alloca +#else +# include +# ifdef __cplusplus +extern "C" +# endif +void *alloca (size_t); +#endif + +#include +#include + +#ifdef _WIN32 +# include +#endif + +#include +#include + +/* define macros and variable for using the eina logging system */ +#define EFREET_MODULE_LOG_DOM _efreet_desktop_log_dom +extern int _efreet_desktop_log_dom; + +#include "Efreet.h" +#include "efreet_private.h" + +/** + * @internal + * The different types of commands in an Exec entry + */ +typedef enum Efreet_Desktop_Command_Flag +{ + EFREET_DESKTOP_EXEC_FLAG_FULLPATH = 0x0001, + EFREET_DESKTOP_EXEC_FLAG_URI = 0x0002 +} Efreet_Desktop_Command_Flag; + +/** + * @internal + * Efreet_Desktop_Command + */ +typedef struct Efreet_Desktop_Command Efreet_Desktop_Command; + +/** + * @internal + * Holds information on a desktop Exec command entry + */ +struct Efreet_Desktop_Command +{ + Efreet_Desktop *desktop; + int num_pending; + + Efreet_Desktop_Command_Flag flags; + + Efreet_Desktop_Command_Cb cb_command; + Efreet_Desktop_Progress_Cb cb_progress; + void *data; + + Eina_List *files; /**< list of Efreet_Desktop_Command_File */ +}; + +/** + * @internal + * Efreet_Desktop_Command_File + */ +typedef struct Efreet_Desktop_Command_File Efreet_Desktop_Command_File; + +/** + * @internal + * Stores information on a file passed to the desktop Exec command + */ +struct Efreet_Desktop_Command_File +{ + Efreet_Desktop_Command *command; + char *dir; + char *file; + char *fullpath; + char *uri; + + int pending; +}; + +static int efreet_desktop_command_file_id = 0; + +static void *efreet_desktop_exec_cb(void *data, Efreet_Desktop *desktop, + char *exec, int remaining); +static int efreet_desktop_command_flags_get(Efreet_Desktop *desktop); +static void *efreet_desktop_command_execs_process(Efreet_Desktop_Command *command, Eina_List *execs); + +static Eina_List *efreet_desktop_command_build(Efreet_Desktop_Command *command); +static void efreet_desktop_command_free(Efreet_Desktop_Command *command); +static char *efreet_desktop_command_append_quoted(char *dest, int *size, + int *len, char *src); +static char *efreet_desktop_command_append_multiple(char *dest, int *size, int *len, + Efreet_Desktop_Command *command, + char type); +static char *efreet_desktop_command_append_single(char *dest, int *size, int *len, + Efreet_Desktop_Command_File *file, + char type); +static char *efreet_desktop_command_append_icon(char *dest, int *size, int *len, + Efreet_Desktop *desktop); + +static Efreet_Desktop_Command_File *efreet_desktop_command_file_process( + Efreet_Desktop_Command *command, + const char *file); +static const char *efreet_desktop_command_file_uri_process(const char *uri); +static void efreet_desktop_command_file_free(Efreet_Desktop_Command_File *file); + +static void efreet_desktop_cb_download_complete(void *data, const char *file, + int status); +static int efreet_desktop_cb_download_progress(void *data, const char *file, + long int dltotal, long int dlnow, + long int ultotal, long int ulnow); + +static char *efreet_desktop_command_path_absolute(const char *path); + +static char *efreet_string_append(char *dest, int *size, + int *len, const char *src); +static char *efreet_string_append_char(char *dest, int *size, + int *len, char c); + + +EAPI void +efreet_desktop_exec(Efreet_Desktop *desktop, Eina_List *files, void *data) +{ + efreet_desktop_command_get(desktop, files, efreet_desktop_exec_cb, data); +} + +EAPI void * +efreet_desktop_command_get(Efreet_Desktop *desktop, Eina_List *files, + Efreet_Desktop_Command_Cb func, void *data) +{ + return efreet_desktop_command_progress_get(desktop, files, func, NULL, data); +} + +EAPI Eina_List * +efreet_desktop_command_local_get(Efreet_Desktop *desktop, Eina_List *files) +{ + Efreet_Desktop_Command *command; + char *file; + Eina_List *execs, *l; + + EINA_SAFETY_ON_NULL_RETURN_VAL(desktop, NULL); + EINA_SAFETY_ON_NULL_RETURN_VAL(desktop->exec, NULL); + + command = NEW(Efreet_Desktop_Command, 1); + if (!command) return 0; + + command->desktop = desktop; + + command->flags = efreet_desktop_command_flags_get(desktop); + /* get the required info for each file passed in */ + if (files) + { + EINA_LIST_FOREACH(files, l, file) + { + Efreet_Desktop_Command_File *dcf; + + dcf = efreet_desktop_command_file_process(command, file); + if (!dcf) continue; + if (dcf->pending) + { + efreet_desktop_command_file_free(dcf); + continue; + } + command->files = eina_list_append(command->files, dcf); + } + } + + execs = efreet_desktop_command_build(command); + efreet_desktop_command_free(command); + + return execs; +} + +EAPI void * +efreet_desktop_command_progress_get(Efreet_Desktop *desktop, Eina_List *files, + Efreet_Desktop_Command_Cb cb_command, + Efreet_Desktop_Progress_Cb cb_progress, + void *data) +{ + Efreet_Desktop_Command *command; + Eina_List *l; + char *file; + void *ret = NULL; + + EINA_SAFETY_ON_NULL_RETURN_VAL(desktop, NULL); + EINA_SAFETY_ON_NULL_RETURN_VAL(desktop->exec, NULL); + EINA_SAFETY_ON_NULL_RETURN_VAL(cb_command, NULL); + + command = NEW(Efreet_Desktop_Command, 1); + if (!command) return NULL; + + command->cb_command = cb_command; + command->cb_progress = cb_progress; + command->data = data; + command->desktop = desktop; + + command->flags = efreet_desktop_command_flags_get(desktop); + /* get the required info for each file passed in */ + if (files) + { + EINA_LIST_FOREACH(files, l, file) + { + Efreet_Desktop_Command_File *dcf; + + dcf = efreet_desktop_command_file_process(command, file); + if (!dcf) continue; + command->files = eina_list_append(command->files, dcf); + command->num_pending += dcf->pending; + } + } + + if (command->num_pending == 0) + { + Eina_List *execs; + + execs = efreet_desktop_command_build(command); + if (execs) + { + ret = efreet_desktop_command_execs_process(command, execs); + eina_list_free(execs); + } + efreet_desktop_command_free(command); + } + + return ret; +} + +static void * +efreet_desktop_exec_cb(void *data, + Efreet_Desktop *desktop __UNUSED__, + char *exec, + int remaining __UNUSED__) +{ + ecore_exe_run(exec, data); + free(exec); + + return NULL; +} + +/** + * @internal + * + * @brief Determine which file related field codes are present in the Exec string of a .desktop + * @params desktop and Efreet Desktop + * @return a bitmask of file field codes present in exec string + */ +static int +efreet_desktop_command_flags_get(Efreet_Desktop *desktop) +{ + int flags = 0; + const char *p; + /* first, determine which fields are present in the Exec string */ + p = strchr(desktop->exec, '%'); + while (p) + { + p++; + switch(*p) + { + case 'f': + case 'F': + flags |= EFREET_DESKTOP_EXEC_FLAG_FULLPATH; + break; + case 'u': + case 'U': + flags |= EFREET_DESKTOP_EXEC_FLAG_URI; + break; + case '%': + p++; + break; + default: + break; + } + + p = strchr(p, '%'); + } +#ifdef SLOPPY_SPEC + /* NON-SPEC!!! this is to work around LOTS of 'broken' .desktop files that + * do not specify %U/%u, %F/F etc. etc. at all. just a command. this is + * unlikely to be fixed in distributions etc. in the long run as gnome/kde + * seem to have workarounds too so no one notices. + */ + if (!flags) flags |= EFREET_DESKTOP_EXEC_FLAG_FULLPATH; +#endif + + return flags; +} + + +/** + * @internal + * + * @brief Call the command callback for each exec in the list + * @param command + * @param execs + */ +static void * +efreet_desktop_command_execs_process(Efreet_Desktop_Command *command, Eina_List *execs) +{ + Eina_List *l; + char *exec; + int num; + void *ret = NULL; + + num = eina_list_count(execs); + EINA_LIST_FOREACH(execs, l, exec) + { + ret = command->cb_command(command->data, command->desktop, exec, --num); + } + return ret; +} + + +/** + * @brief Builds the actual exec string from the raw string and a list of + * processed filename information. The callback passed in to + * efreet_desktop_command_get is called for each exec string created. + * + * @param command the command to build + * @return a list of executable strings + */ +static Eina_List * +efreet_desktop_command_build(Efreet_Desktop_Command *command) +{ + Eina_List *execs = NULL; + const Eina_List *l; + char *exec; + + /* if the Exec field appends multiple, that will run the list to the end, + * causing this loop to only run once. otherwise, this loop will generate a + * command for each file in the list. if the list is empty, this + * will run once, removing any file field codes */ + l = command->files; + do + { + const char *p; + int len = 0; + int size = PATH_MAX; + int file_added = 0; + Efreet_Desktop_Command_File *file = eina_list_data_get(l); + int single; + + exec = malloc(size); + if (!exec) goto error; + p = command->desktop->exec; + len = 0; + + single = 0; + while (*p) + { + if (len >= size - 1) + { + char *tmp; + + size = len + 1024; + tmp = realloc(exec, size); + if (!tmp) goto error; + exec = tmp; + } + + /* XXX handle fields inside quotes? */ + if (*p == '%') + { + p++; + switch (*p) + { + case 'f': + case 'u': + case 'd': + case 'n': + if (file) + { + exec = efreet_desktop_command_append_single(exec, &size, + &len, file, *p); + if (!exec) goto error; + file_added = 1; + single = 1; + } + break; + case 'F': + case 'U': + case 'D': + case 'N': + if (file) + { + exec = efreet_desktop_command_append_multiple(exec, &size, + &len, command, *p); + fprintf(stderr, "EXE: '%s'\n", exec); + if (!exec) goto error; + file_added = 1; + } + break; + case 'i': + exec = efreet_desktop_command_append_icon(exec, &size, &len, + command->desktop); + if (!exec) goto error; + break; + case 'c': + exec = efreet_desktop_command_append_quoted(exec, &size, &len, + command->desktop->name); + if (!exec) goto error; + break; + case 'k': + exec = efreet_desktop_command_append_quoted(exec, &size, &len, + command->desktop->orig_path); + if (!exec) goto error; + break; + case 'v': + case 'm': + WRN("[Efreet]: Deprecated conversion char: '%c' in file '%s'", + *p, command->desktop->orig_path); + break; + case '%': + exec[len++] = *p; + break; + default: +#ifdef STRICT_SPEC + WRN("[Efreet_desktop]: Unknown conversion character: '%c'", *p); +#endif + break; + } + } + else exec[len++] = *p; + p++; + } + +#ifdef SLOPPY_SPEC + /* NON-SPEC!!! this is to work around LOTS of 'broken' .desktop files that + * do not specify %U/%u, %F/F etc. etc. at all. just a command. this is + * unlikely to be fixed in distributions etc. in the long run as gnome/kde + * seem to have workarounds too so no one notices. + */ + if ((file) && (!file_added)) + { + WRN("Efreet_desktop: %s\n" + " command: %s\n" + " has no file path/uri spec info for executing this app WITH a\n" + " file/uri as a parameter. This is unlikely to be the intent.\n" + " please check the .desktop file and fix it by adding a %%U or %%F\n" + " or something appropriate.", + command->desktop->orig_path, command->desktop->exec); + if (len >= size - 1) + { + char *tmp; + size = len + 1024; + tmp = realloc(exec, size); + if (!tmp) goto error; + exec = tmp; + } + exec[len++] = ' '; + exec = efreet_desktop_command_append_multiple(exec, &size, + &len, command, 'F'); + if (!exec) goto error; + file_added = 1; + } +#endif + exec[len++] = '\0'; + + if ((single) || (!execs)) + { + execs = eina_list_append(execs, exec); + exec = NULL; + } + + /* If no file was added, then the Exec field doesn't contain any file + * fields (fFuUdDnN). We only want to run the app once in this case. */ + if (!file_added) break; + } + while ((l = eina_list_next(l))); + + return execs; +error: + IF_FREE(exec); + EINA_LIST_FREE(execs, exec) + free(exec); + return NULL; +} + +static void +efreet_desktop_command_free(Efreet_Desktop_Command *command) +{ + Efreet_Desktop_Command_File *dcf; + + if (!command) return; + + while (command->files) + { + dcf = eina_list_data_get(command->files); + efreet_desktop_command_file_free(dcf); + command->files = eina_list_remove_list(command->files, + command->files); + } + FREE(command); +} + +static char * +efreet_desktop_command_append_quoted(char *dest, int *size, int *len, char *src) +{ + if (!src) return dest; + dest = efreet_string_append(dest, size, len, "'"); + if (!dest) return NULL; + + /* single quotes in src need to be escaped */ + if (strchr(src, '\'')) + { + char *p; + p = src; + while (*p) + { + if (*p == '\'') + { + dest = efreet_string_append(dest, size, len, "\'\\\'"); + if (!dest) return NULL; + } + + dest = efreet_string_append_char(dest, size, len, *p); + if (!dest) return NULL; + p++; + } + } + else + { + dest = efreet_string_append(dest, size, len, src); + if (!dest) return NULL; + } + + dest = efreet_string_append(dest, size, len, "'"); + if (!dest) return NULL; + + return dest; +} + +static char * +efreet_desktop_command_append_multiple(char *dest, int *size, int *len, + Efreet_Desktop_Command *command, + char type) +{ + Efreet_Desktop_Command_File *file; + Eina_List *l; + int first = 1; + + if (!command->files) return dest; + + EINA_LIST_FOREACH(command->files, l, file) + { + if (first) + first = 0; + else + { + dest = efreet_string_append_char(dest, size, len, ' '); + if (!dest) return NULL; + } + + dest = efreet_desktop_command_append_single(dest, size, len, + file, tolower(type)); + if (!dest) return NULL; + } + + return dest; +} + +static char * +efreet_desktop_command_append_single(char *dest, int *size, int *len, + Efreet_Desktop_Command_File *file, + char type) +{ + char *str; + switch(type) + { + case 'f': + str = file->fullpath; + break; + case 'u': + str = file->uri; + break; + case 'd': + str = file->dir; + break; + case 'n': + str = file->file; + break; + default: + ERR("Invalid type passed to efreet_desktop_command_append_single:" + " '%c'", type); + return dest; + } + + if (!str) return dest; + + dest = efreet_desktop_command_append_quoted(dest, size, len, str); + if (!dest) return NULL; + + return dest; +} + +static char * +efreet_desktop_command_append_icon(char *dest, int *size, int *len, + Efreet_Desktop *desktop) +{ + if (!desktop->icon || !desktop->icon[0]) return dest; + + dest = efreet_string_append(dest, size, len, "--icon "); + if (!dest) return NULL; + dest = efreet_desktop_command_append_quoted(dest, size, len, desktop->icon); + if (!dest) return NULL; + + return dest; +} + +/** + * @param command the Efreet_Desktop_Comand that this file is for + * @param file the filname as either an absolute path, relative path, or URI + */ +static Efreet_Desktop_Command_File * +efreet_desktop_command_file_process(Efreet_Desktop_Command *command, const char *file) +{ + Efreet_Desktop_Command_File *f; + const char *uri, *base; + int nonlocal = 0; +/* + DBG("FLAGS: %d, %d, %d, %d\n", + command->flags & EFREET_DESKTOP_EXEC_FLAG_FULLPATH ? 1 : 0, + command->flags & EFREET_DESKTOP_EXEC_FLAG_URI ? 1 : 0); +*/ + f = NEW(Efreet_Desktop_Command_File, 1); + if (!f) return NULL; + + f->command = command; + + /* handle uris */ + if (!strncmp(file, "http://", 7) || !strncmp(file, "ftp://", 6)) + { + uri = file; + base = ecore_file_file_get(file); + + nonlocal = 1; + } + else if (!strncmp(file, "file:", 5)) + { + file = efreet_desktop_command_file_uri_process(file); + if (!file) + { + efreet_desktop_command_file_free(f); + return NULL; + } + } + + if (nonlocal) + { + /* process non-local uri */ + if (command->flags & EFREET_DESKTOP_EXEC_FLAG_FULLPATH) + { + char buf[PATH_MAX]; + + snprintf(buf, sizeof(buf), "/tmp/%d-%d-%s", getpid(), + efreet_desktop_command_file_id++, base); + f->fullpath = strdup(buf); + f->pending = 1; + + ecore_file_download(uri, f->fullpath, efreet_desktop_cb_download_complete, + efreet_desktop_cb_download_progress, f, NULL); + } + + if (command->flags & EFREET_DESKTOP_EXEC_FLAG_URI) + f->uri = strdup(uri); + } + else + { + char *absol = efreet_desktop_command_path_absolute(file); + if (!absol) goto error; + /* process local uri/path */ + if (command->flags & EFREET_DESKTOP_EXEC_FLAG_FULLPATH) + f->fullpath = strdup(absol); + + if (command->flags & EFREET_DESKTOP_EXEC_FLAG_URI) + { + const char *buf; + Efreet_Uri ef_uri; + ef_uri.protocol = "file"; + ef_uri.hostname = ""; + ef_uri.path = absol; + buf = efreet_uri_encode(&ef_uri); + + f->uri = strdup(buf); + + eina_stringshare_del(buf); + } + + free(absol); + } +#if 0 + INF(" fullpath: %s", f->fullpath); + INF(" uri: %s", f->uri); + INF(" dir: %s", f->dir); + INF(" file: %s", f->file); +#endif + return f; +error: + IF_FREE(f); + return NULL; +} + +/** + * @brief Find the local path portion of a file uri. + * @param uri a uri beginning with "file" + * @return the location of the path portion of the uri, + * or NULL if the file is not on this machine + */ +static const char * +efreet_desktop_command_file_uri_process(const char *uri) +{ + const char *path = NULL; + int len = strlen(uri); + + /* uri:foo/bar => relative path foo/bar*/ + if (len >= 4 && uri[5] != '/') + path = uri + strlen("file:"); + + /* uri:/foo/bar => absolute path /foo/bar */ + else if (len >= 5 && uri[6] != '/') + path = uri + strlen("file:"); + + /* uri://foo/bar => absolute path /bar on machine foo */ + else if (len >= 6 && uri[7] != '/') + { + char *tmp, *p; + char hostname[PATH_MAX]; + size_t len2; + + len2 = strlen(uri + 7) + 1; + tmp = alloca(len2); + memcpy(tmp, uri + 7, len2); + p = strchr(tmp, '/'); + if (p) + { + *p = '\0'; + if (!strcmp(tmp, "localhost")) + path = uri + strlen("file://localhost"); + else + { + int ret; + + ret = gethostname(hostname, PATH_MAX); + if ((ret == 0) && !strcmp(tmp, hostname)) + path = uri + strlen("file://") + strlen(hostname); + } + } + } + + /* uri:///foo/bar => absolute path /foo/bar on local machine */ + else if (len >= 7) + path = uri + strlen("file://"); + + return path; +} + +static void +efreet_desktop_command_file_free(Efreet_Desktop_Command_File *file) +{ + if (!file) return; + + IF_FREE(file->fullpath); + IF_FREE(file->uri); + IF_FREE(file->dir); + IF_FREE(file->file); + + FREE(file); +} + + +static void +efreet_desktop_cb_download_complete(void *data, const char *file __UNUSED__, + int status __UNUSED__) +{ + Efreet_Desktop_Command_File *f; + + f = data; + + /* XXX check status... error handling, etc */ + f->pending = 0; + f->command->num_pending--; + + if (f->command->num_pending <= 0) + { + Eina_List *execs; + + execs = efreet_desktop_command_build(f->command); + if (execs) + { + /* TODO: Need to handle the return value from efreet_desktop_command_execs_process */ + efreet_desktop_command_execs_process(f->command, execs); + eina_list_free(execs); + } + efreet_desktop_command_free(f->command); + } +} + +static int +efreet_desktop_cb_download_progress(void *data, + const char *file __UNUSED__, + long int dltotal, long int dlnow, + long int ultotal __UNUSED__, + long int ulnow __UNUSED__) +{ + Efreet_Desktop_Command_File *dcf; + + dcf = data; + if (dcf->command->cb_progress) + return dcf->command->cb_progress(dcf->command->data, + dcf->command->desktop, + dcf->uri, dltotal, dlnow); + + return 0; +} + +/** + * @brief Build an absolute path from an absolute or relative one. + * @param path an absolute or relative path + * @return an allocated absolute path (must be freed) + */ +static char * +efreet_desktop_command_path_absolute(const char *path) +{ + char *buf; + int size = PATH_MAX; + int len = 0; + + /* relative url */ + if (path[0] != '/') + { + if (!(buf = malloc(size))) return NULL; + if (!getcwd(buf, size)) + { + FREE(buf); + return NULL; + } + len = strlen(buf); + + if (buf[len-1] != '/') buf = efreet_string_append(buf, &size, &len, "/"); + if (!buf) return NULL; + buf = efreet_string_append(buf, &size, &len, path); + if (!buf) return NULL; + + return buf; + } + + /* just dup an already absolute buffer */ + return strdup(path); +} + +/** + * Append a string to a buffer, reallocating as necessary. + */ +static char * +efreet_string_append(char *dest, int *size, int *len, const char *src) +{ + int l; + int off = 0; + + l = eina_strlcpy(dest + *len, src, *size - *len); + + while (l > *size - *len) + { + char *tmp; + /* we successfully appended this much */ + off += *size - *len - 1; + *len = *size - 1; + *size += 1024; + tmp = realloc(dest, *size); + if (!tmp) + { + free(dest); + return NULL; + } + dest = tmp; + *(dest + *len) = '\0'; + + l = eina_strlcpy(dest + *len, src + off, *size - *len); + } + *len += l; + + return dest; +} + +static char * +efreet_string_append_char(char *dest, int *size, int *len, char c) +{ + if (*len >= *size - 1) + { + char *tmp; + *size += 1024; + tmp = realloc(dest, *size); + if (!tmp) + { + free(dest); + return NULL; + } + dest = tmp; + } + + dest[(*len)++] = c; + dest[*len] = '\0'; + + return dest; +} + diff --git a/src/lib/efreet_icon.c b/src/lib/efreet_icon.c new file mode 100644 index 0000000..526e0ec --- /dev/null +++ b/src/lib/efreet_icon.c @@ -0,0 +1,910 @@ +#ifdef HAVE_CONFIG_H +# include +#endif + +#undef alloca +#ifdef HAVE_ALLOCA_H +# include +#elif defined __GNUC__ +# define alloca __builtin_alloca +#elif defined _AIX +# define alloca __alloca +#elif defined _MSC_VER +# include +# define alloca _alloca +#else +# include +# ifdef __cplusplus +extern "C" +# endif +void *alloca (size_t); +#endif + +#include +#include + +/* define macros and variable for using the eina logging system */ +#define EFREET_MODULE_LOG_DOM _efreet_icon_log_dom +static int _efreet_icon_log_dom = -1; + +#include "Efreet.h" +#include "efreet_private.h" + +static const char *efreet_icon_deprecated_user_dir = NULL; +static const char *efreet_icon_user_dir = NULL; +static Eina_List *efreet_icon_extensions = NULL; +static Eina_List *efreet_extra_icon_dirs = NULL; + +static Eina_Hash *change_monitors = NULL; + +typedef struct Efreet_Icon_Cache Efreet_Icon_Cache; +struct Efreet_Icon_Cache +{ + const char *key; + const char *path; + time_t lasttime; +}; + +static char *efreet_icon_remove_extension(const char *icon); + +static Efreet_Icon *efreet_icon_new(const char *path); +static void efreet_icon_populate(Efreet_Icon *icon, const char *file); + +static const char *efreet_icon_lookup_icon(Efreet_Cache_Icon *icon, unsigned int size); +static const char *efreet_icon_list_lookup_icon(Efreet_Icon_Theme *theme, Eina_List *icons, unsigned int size); +static int efreet_icon_size_match(Efreet_Cache_Icon_Element *elem, unsigned int size); +static double efreet_icon_size_distance(Efreet_Cache_Icon_Element *elem, unsigned int size); +static const char *efreet_icon_lookup_path(Efreet_Cache_Icon_Element *elem); +static const char *efreet_icon_lookup_path_path(Efreet_Cache_Icon_Element *elem, const char *path); +static const char *efreet_icon_fallback_lookup_path(Efreet_Cache_Fallback_Icon *icon); +static const char *efreet_icon_fallback_lookup_path_path(Efreet_Cache_Fallback_Icon *icon, + const char *path); + +static void efreet_icon_changes_listen(void); +static void efreet_icon_changes_monitor_add(const char *path); +static void efreet_icon_changes_cb(void *data, Ecore_File_Monitor *em, + Ecore_File_Event event, const char *path); + + +/** + * @internal + * @return Returns 1 on success or 0 on failure + * @brief Initializes the icon system + */ +int +efreet_icon_init(void) +{ + const char *default_exts[] = {".png", ".xpm", ".svg", NULL}; + int i; + + _efreet_icon_log_dom = eina_log_domain_register + ("efreet_icon", EFREET_DEFAULT_LOG_COLOR); + if (_efreet_icon_log_dom < 0) + return 0; + + /* setup the default extension list */ + for (i = 0; default_exts[i]; i++) + efreet_icon_extensions = eina_list_append(efreet_icon_extensions, eina_stringshare_add(default_exts[i])); + + efreet_icon_changes_listen(); + + efreet_extra_icon_dirs = NULL; + + return 1; +} + +/** + * @internal + * @return Returns no value + * @brief Shuts down the icon system + */ +void +efreet_icon_shutdown(void) +{ + IF_RELEASE(efreet_icon_user_dir); + IF_RELEASE(efreet_icon_deprecated_user_dir); + + IF_FREE_LIST(efreet_icon_extensions, eina_stringshare_del); + efreet_extra_icon_dirs = eina_list_free(efreet_extra_icon_dirs); + + eina_log_domain_unregister(_efreet_icon_log_dom); + _efreet_icon_log_dom = -1; + IF_FREE_HASH(change_monitors); +} + +EAPI const char * +efreet_icon_deprecated_user_dir_get(void) +{ + const char *user; + char *tmp; + int len; + + if (efreet_icon_deprecated_user_dir) return efreet_icon_deprecated_user_dir; + + user = efreet_home_dir_get(); + len = strlen(user) + strlen("/.icons") + 1; + tmp = alloca(len); + snprintf(tmp, len, "%s/.icons", user); + + efreet_icon_deprecated_user_dir = eina_stringshare_add_length(tmp, len - 1); + + return efreet_icon_deprecated_user_dir; +} + +EAPI const char * +efreet_icon_user_dir_get(void) +{ + const char *user; + char *tmp; + int len; + + if (efreet_icon_user_dir) return efreet_icon_user_dir; + + user = efreet_data_home_get(); + len = strlen(user) + strlen("/icons") + 1; + tmp = alloca(len); + snprintf(tmp, len, "%s/icons", user); + + efreet_icon_user_dir = eina_stringshare_add_length(tmp, len - 1); + + return efreet_icon_user_dir; +} + +EAPI void +efreet_icon_extension_add(const char *ext) +{ + Eina_List *l; + + EINA_SAFETY_ON_NULL_RETURN(ext); + + ext = eina_stringshare_add(ext); + + if ((l = eina_list_data_find_list(efreet_icon_extensions, ext))) + { + efreet_icon_extensions = eina_list_promote_list(efreet_icon_extensions, l); + eina_stringshare_del(ext); + } + else + efreet_icon_extensions = eina_list_prepend(efreet_icon_extensions, ext); +} + +EAPI Eina_List ** +efreet_icon_extra_list_get(void) +{ + return &efreet_extra_icon_dirs; +} + +EAPI Eina_List * +efreet_icon_extensions_list_get(void) +{ + return efreet_icon_extensions; +} + +EAPI Eina_List * +efreet_icon_theme_list_get(void) +{ + return efreet_cache_icon_theme_list(); +} + +EAPI Efreet_Icon_Theme * +efreet_icon_theme_find(const char *theme_name) +{ + if (!theme_name) return NULL; + + return efreet_cache_icon_theme_find(theme_name); +} + +/** + * @internal + * @param icon The icon name to strip extension + * @return Extension removed if in list of extensions, else untouched. + * @brief Removes extension from icon name if in list of extensions. + */ +static char * +efreet_icon_remove_extension(const char *icon) +{ + Eina_List *l; + char *tmp = NULL, *ext = NULL; + + if (!icon) return NULL; + + tmp = strdup(icon); + ext = strrchr(tmp, '.'); + if (ext) + { + const char *ext2; + EINA_LIST_FOREACH(efreet_icon_extensions, l, ext2) + { + if (!strcmp(ext, ext2)) + { +#ifdef STRICT_SPEC + WRN("[Efreet]: Requesting an icon with an extension: %s", + icon); +#endif + *ext = '\0'; + break; + } + } + } + + return tmp; +} + +EAPI const char * +efreet_icon_path_find(const char *theme_name, const char *icon, unsigned int size) +{ + char *tmp; + const char *value = NULL; + Efreet_Icon_Theme *theme; + + EINA_SAFETY_ON_NULL_RETURN_VAL(icon, NULL); + + theme = efreet_icon_theme_find(theme_name); + +#ifdef SLOPPY_SPEC + tmp = efreet_icon_remove_extension(icon); + if (!tmp) return NULL; +#else + tmp = icon; +#endif + + if (theme) + { + Efreet_Cache_Icon *cache; + cache = efreet_cache_icon_find(theme, tmp); + value = efreet_icon_lookup_icon(cache, size); + if (!value) INF("lookup for `%s` failed in theme `%s` with %p.", icon, theme_name, cache); + } + + /* we didn't find the icon in the theme or in the inherited directories + * then just look for a non theme icon + */ + if (!value) + { + Efreet_Cache_Fallback_Icon *cache; + + cache = efreet_cache_icon_fallback_find(tmp); + value = efreet_icon_fallback_lookup_path(cache); + if (!value) INF("lookup for `%s` failed in fallback too with %p.", icon, cache); + } + +#ifdef SLOPPY_SPEC + FREE(tmp); +#endif + return value; +} + +EAPI const char * +efreet_icon_list_find(const char *theme_name, Eina_List *icons, + unsigned int size) +{ + Eina_List *l; + Eina_List *tmps = NULL; + const char *icon = NULL; + const char *value = NULL; + char *data; + Efreet_Icon_Theme *theme; + + EINA_SAFETY_ON_NULL_RETURN_VAL(icons, NULL); + + theme = efreet_icon_theme_find(theme_name); + +#ifdef SLOPPY_SPEC + EINA_LIST_FOREACH(icons, l, icon) + { + data = efreet_icon_remove_extension(icon); + if (!data) return NULL; + tmps = eina_list_append(tmps, data); + } +#else + tmps = icons; +#endif + + if (theme) + { + Eina_List *tmps2 = NULL; + Efreet_Cache_Icon *cache; + + EINA_LIST_FOREACH(tmps, l, icon) + { + cache = efreet_cache_icon_find(theme, icon); + if (cache) + { + /* If the icon is in the asked for theme, return it */ + if (!strcmp(cache->theme, theme->name.internal)) + { + value = efreet_icon_lookup_icon(cache, size); + break; + } + else + tmps2 = eina_list_append(tmps2, cache); + } + } + if (tmps2) + { + if (!value) + value = efreet_icon_list_lookup_icon(theme, tmps2, size); + eina_list_free(tmps2); + } + } + + /* we didn't find the icons in the theme or in the inherited directories + * then just look for a non theme icon + */ + if (!value) + { + Efreet_Cache_Fallback_Icon *cache; + EINA_LIST_FOREACH(tmps, l, icon) + { + cache = efreet_cache_icon_fallback_find(icon); + value = efreet_icon_fallback_lookup_path(cache); + if (value) + break; + } + } + +#ifdef SLOPPY_SPEC + EINA_LIST_FREE(tmps, data) + free(data); +#endif + + return value; +} + +EAPI Efreet_Icon * +efreet_icon_find(const char *theme_name, const char *icon, unsigned int size) +{ + const char *path; + + EINA_SAFETY_ON_NULL_RETURN_VAL(icon, NULL); + + path = efreet_icon_path_find(theme_name, icon, size); + if (path) + { + Efreet_Icon *ic; + + ic = efreet_icon_new(path); + return ic; + } + + return NULL; +} + +/** + * @internal + * @return Returns a new Efreet_Icon struct on success or NULL on failure + * @brief Creates a new Efreet_Icon struct + */ +static Efreet_Icon * +efreet_icon_new(const char *path) +{ + Efreet_Icon *icon; + char *p; + + icon = NEW(Efreet_Icon, 1); + if (!icon) return NULL; + icon->path = eina_stringshare_add(path); + + /* load the .icon file if it's available */ + p = strrchr(icon->path, '.'); + if (p) + { + char ico_path[PATH_MAX]; + + *p = '\0'; + + snprintf(ico_path, sizeof(ico_path), "%s.icon", icon->path); + *p = '.'; + + if (ecore_file_exists(ico_path)) + efreet_icon_populate(icon, ico_path); + } + + if (!icon->name) + { + const char *file; + + file = ecore_file_file_get(icon->path); + p = strrchr(icon->path, '.'); + if (p) *p = '\0'; + icon->name = eina_stringshare_add(file); + if (p) *p = '.'; + } + + return icon; +} + +EAPI void +efreet_icon_free(Efreet_Icon *icon) +{ + if (!icon) return; + + icon->ref_count --; + if (icon->ref_count > 0) return; + + IF_RELEASE(icon->path); + IF_RELEASE(icon->name); + IF_FREE_LIST(icon->attach_points, free); + + FREE(icon); +} + +/** + * @internal + * @param icon The icon to populate + * @param file The file to populate from + * @return Returns no value + * @brief Tries to populate the icon information from the given file + */ +static void +efreet_icon_populate(Efreet_Icon *icon, const char *file) +{ + Efreet_Ini *ini; + const char *tmp; + + ini = efreet_ini_new(file); + if (!ini) return; + if (!ini->data) + { + efreet_ini_free(ini); + return; + } + + efreet_ini_section_set(ini, "Icon Data"); + tmp = efreet_ini_localestring_get(ini, "DisplayName"); + if (tmp) icon->name = eina_stringshare_add(tmp); + + tmp = efreet_ini_string_get(ini, "EmbeddedTextRectangle"); + if (tmp) + { + int points[4]; + char *t, *s, *p; + int i; + size_t len; + + len = strlen(tmp) + 1; + t = alloca(len); + memcpy(t, tmp, len); + s = t; + for (i = 0; i < 4; i++) + { + if (s) + { + p = strchr(s, ','); + + if (p) *p = '\0'; + points[i] = atoi(s); + + if (p) s = ++p; + else s = NULL; + } + else + { + points[i] = 0; + } + } + + icon->has_embedded_text_rectangle = 1; + icon->embedded_text_rectangle.x0 = points[0]; + icon->embedded_text_rectangle.y0 = points[1]; + icon->embedded_text_rectangle.x1 = points[2]; + icon->embedded_text_rectangle.y1 = points[3]; + } + + tmp = efreet_ini_string_get(ini, "AttachPoints"); + if (tmp) + { + char *t, *s, *p; + size_t len; + + len = strlen(tmp) + 1; + t = alloca(len); + memcpy(t, tmp, len); + s = t; + while (s) + { + Efreet_Icon_Point *point; + + p = strchr(s, ','); + /* If this happens there is something wrong with the .icon file */ + if (!p) break; + + point = NEW(Efreet_Icon_Point, 1); + if (!point) goto error; + + *p = '\0'; + point->x = atoi(s); + + s = ++p; + p = strchr(s, '|'); + if (p) *p = '\0'; + + point->y = atoi(s); + + icon->attach_points = eina_list_append(icon->attach_points, point); + + if (p) s = ++p; + else s = NULL; + } + } + +error: + efreet_ini_free(ini); +} + +static const char * +efreet_icon_lookup_icon(Efreet_Cache_Icon *icon, unsigned int size) +{ + const char *path = NULL; + double minimal_distance = INT_MAX; + unsigned int ret_size = 0; + unsigned int i; + + if (!icon) return NULL; + + /* search for allowed size == requested size */ + for (i = 0; i < icon->icons_count; ++i) + { + if (!efreet_icon_size_match(icon->icons[i], size)) continue; + path = efreet_icon_lookup_path(icon->icons[i]); + if (path) return path; + } + + /* search for any icon that matches */ + for (i = 0; i < icon->icons_count; ++i) + { + const char *tmp = NULL; + double distance; + + distance = efreet_icon_size_distance(icon->icons[i], size); + if (distance > minimal_distance) continue; + // prefer downsizing + if ((distance == minimal_distance) && (icon->icons[i]->normal < ret_size)) continue; + + tmp = efreet_icon_lookup_path(icon->icons[i]); + + if (tmp) + { + path = tmp; + minimal_distance = distance; + ret_size = icon->icons[i]->normal; + } + } + + return path; +} + +static const char * +efreet_icon_list_lookup_icon(Efreet_Icon_Theme *theme, Eina_List *icons, unsigned int size) +{ + const char *value = NULL; + Efreet_Cache_Icon *cache; + Eina_List *l; + + EINA_LIST_FOREACH(icons, l, cache) + { + if (!strcmp(cache->theme, theme->name.internal)) + { + value = efreet_icon_lookup_icon(cache, size); + if (value) break; + } + } + if (value) return value; + if (theme->inherits) + { + const char *parent; + EINA_LIST_FOREACH(theme->inherits, l, parent) + { + Efreet_Icon_Theme *parent_theme; + + parent_theme = efreet_icon_theme_find(parent); + if ((!parent_theme) || (parent_theme == theme)) continue; + + value = efreet_icon_list_lookup_icon(parent_theme, icons, size); + if (value) break; + } + } + /* if this isn't the hicolor theme, and we have no other fallbacks + * check hicolor */ + else if (strcmp(theme->name.internal, "hicolor")) + { + Efreet_Icon_Theme *parent_theme; + + parent_theme = efreet_icon_theme_find("hicolor"); + if (parent_theme) + value = efreet_icon_list_lookup_icon(parent_theme, icons, size); + } + return value; +} + +static int +efreet_icon_size_match(Efreet_Cache_Icon_Element *elem, unsigned int size) +{ + if (elem->type == EFREET_ICON_SIZE_TYPE_FIXED) + return (elem->normal == size); + + if ((elem->type == EFREET_ICON_SIZE_TYPE_SCALABLE) || + (elem->type == EFREET_ICON_SIZE_TYPE_THRESHOLD)) + return ((elem->min < size) && (size < elem->max)); + + return 0; +} + +static double +efreet_icon_size_distance(Efreet_Cache_Icon_Element *elem, unsigned int size) +{ + if (elem->type == EFREET_ICON_SIZE_TYPE_FIXED) + return (abs(elem->normal - size)); + + if ((elem->type == EFREET_ICON_SIZE_TYPE_SCALABLE) || + (elem->type == EFREET_ICON_SIZE_TYPE_THRESHOLD)) + { +#ifdef STRICT_SPEC + if (size < elem->min) + return (elem->min - size); + if (elem->max < size) + return (size - elem->max); +#else + if (size < elem->min) + return (elem->min / (double)size); + if (elem->max < size) + return (size / (double)elem->max); +#endif + } + + return 0; +} + +static const char * +efreet_icon_lookup_path(Efreet_Cache_Icon_Element *elem) +{ + Eina_List *xdg_dirs, *l; + const char *path; + const char *dir; + char buf[PATH_MAX]; + + if (elem->paths_count == 1) + { + const char *pp, *ext; + + pp = strrchr(elem->paths[0], '.'); + if (!pp) return NULL; + + EINA_LIST_FOREACH(efreet_icon_extensions, l, ext) + if (!strcmp(pp, ext)) + return elem->paths[0]; + return NULL; + } + + path = efreet_icon_lookup_path_path(elem, efreet_icon_deprecated_user_dir_get()); + if (path) return path; + + path = efreet_icon_lookup_path_path(elem, efreet_icon_user_dir_get()); + if (path) return path; + +#if 0 + EINA_LIST_FOREACH(efreet_extra_icon_dirs, l, dir) + { + path = efreet_icon_lookup_path_path(elem, dir); + if (path) return path; + } +#endif + + xdg_dirs = efreet_data_dirs_get(); + + EINA_LIST_FOREACH(xdg_dirs, l, dir) + { + snprintf(buf, sizeof(buf), "%s/icons", dir); + + path = efreet_icon_lookup_path_path(elem, buf); + if (path) return path; + } + + return NULL; +} + +static const char * +efreet_icon_lookup_path_path(Efreet_Cache_Icon_Element *elem, const char *path) +{ + Eina_List *ll; + const char *ext, *pp; + unsigned int i; + int len; + + len = strlen(path); + + for (i = 0; i < elem->paths_count; ++i) + { + if (strncmp(path, elem->paths[i], len)) continue; + pp = strrchr(elem->paths[i], '.'); + if (!pp) continue; + + EINA_LIST_FOREACH(efreet_icon_extensions, ll, ext) + if (!strcmp(pp, ext)) + return elem->paths[i]; + } + + return NULL; +} + +static const char * +efreet_icon_fallback_lookup_path(Efreet_Cache_Fallback_Icon *icon) +{ + const char *path; + Eina_List *xdg_dirs, *l; + const char *dir; + char buf[PATH_MAX]; + + if (!icon) return NULL; + + if (icon->icons_count == 1) + { + const char *pp, *ext; + + pp = strrchr(icon->icons[0], '.'); + if (!pp) return NULL; + + EINA_LIST_FOREACH(efreet_icon_extensions, l, ext) + if (!strcmp(pp, ext)) + return icon->icons[0]; + return NULL; + } + + path = efreet_icon_fallback_lookup_path_path(icon, efreet_icon_deprecated_user_dir_get()); + if (path) return path; + + path = efreet_icon_fallback_lookup_path_path(icon, efreet_icon_user_dir_get()); + if (path) return path; + + EINA_LIST_FOREACH(efreet_extra_icon_dirs, l, dir) + { + path = efreet_icon_fallback_lookup_path_path(icon, dir); + if (path) return path; + } + + xdg_dirs = efreet_data_dirs_get(); + + EINA_LIST_FOREACH(xdg_dirs, l, dir) + { + snprintf(buf, sizeof(buf), "%s/icons", dir); + + path = efreet_icon_fallback_lookup_path_path(icon, buf); + if (path) return path; + } + +#ifndef STRICT_SPEC + EINA_LIST_FOREACH(xdg_dirs, l, dir) + { + snprintf(buf, sizeof(buf), "%s/pixmaps", dir); + + path = efreet_icon_fallback_lookup_path_path(icon, buf); + if (path) return path; + } +#endif + + path = efreet_icon_fallback_lookup_path_path(icon, "/usr/share/pixmaps"); + if (path) return path; + + return NULL; +} + +static const char * +efreet_icon_fallback_lookup_path_path(Efreet_Cache_Fallback_Icon *icon, const char *path) +{ + Eina_List *ll; + const char *ext, *pp; + unsigned int i; + int len; + + len = strlen(path); + + for (i = 0; i < icon->icons_count; ++i) + { + if (strncmp(path, icon->icons[i], len)) continue; + + pp = strrchr(icon->icons[i], '.'); + if (!pp) continue; + + EINA_LIST_FOREACH(efreet_icon_extensions, ll, ext) + if (!strcmp(pp, ext)) + return icon->icons[i]; + } + + return NULL; +} + +static void +efreet_icon_changes_listen(void) +{ + Eina_List *l; + Eina_List *xdg_dirs; + char buf[PATH_MAX]; + const char *dir; + + if (!efreet_cache_update) return; + + change_monitors = eina_hash_string_superfast_new(EINA_FREE_CB(ecore_file_monitor_del)); + if (!change_monitors) return; + + efreet_icon_changes_monitor_add(efreet_icon_deprecated_user_dir_get()); + efreet_icon_changes_monitor_add(efreet_icon_user_dir_get()); + EINA_LIST_FOREACH(efreet_extra_icon_dirs, l, dir) + efreet_icon_changes_monitor_add(dir); + + xdg_dirs = efreet_data_dirs_get(); + EINA_LIST_FOREACH(xdg_dirs, l, dir) + { + snprintf(buf, sizeof(buf), "%s/icons", dir); + efreet_icon_changes_monitor_add(buf); + } + +#ifndef STRICT_SPEC + EINA_LIST_FOREACH(xdg_dirs, l, dir) + { + snprintf(buf, sizeof(buf), "%s/pixmaps", dir); + efreet_icon_changes_monitor_add(buf); + } +#endif + + efreet_icon_changes_monitor_add("/usr/share/pixmaps"); +} + +static void +efreet_icon_changes_monitor_add(const char *path) +{ + char rp[PATH_MAX]; + + if (!realpath(path, rp)) return; + if (!ecore_file_is_dir(rp)) return; + if (eina_hash_find(change_monitors, rp)) return; + eina_hash_add(change_monitors, rp, + ecore_file_monitor_add(rp, + efreet_icon_changes_cb, + NULL)); + + if (ecore_file_is_dir(rp)) + { + Eina_Iterator *it; + const char *ent; + + it = eina_file_ls(rp); + if (!it) return; + EINA_ITERATOR_FOREACH(it, ent) + { + if (!realpath(ent, rp)) continue; + if (!ecore_file_is_dir(rp)) continue; + eina_hash_add(change_monitors, rp, + ecore_file_monitor_add(rp, + efreet_icon_changes_cb, + NULL)); + } + eina_iterator_free(it); + } +} + +static void +efreet_icon_changes_cb(void *data __UNUSED__, Ecore_File_Monitor *em __UNUSED__, + Ecore_File_Event event, const char *path) +{ + /* TODO: If we get a stale symlink, we need to rerun cache creation */ + switch (event) + { + case ECORE_FILE_EVENT_NONE: + /* noop */ + break; + + case ECORE_FILE_EVENT_CREATED_FILE: + case ECORE_FILE_EVENT_DELETED_FILE: + case ECORE_FILE_EVENT_MODIFIED: + case ECORE_FILE_EVENT_CLOSED: + case ECORE_FILE_EVENT_DELETED_DIRECTORY: + case ECORE_FILE_EVENT_CREATED_DIRECTORY: + efreet_cache_icon_update(); + break; + + case ECORE_FILE_EVENT_DELETED_SELF: + eina_hash_del_by_key(change_monitors, path); + efreet_cache_icon_update(); + break; + } +} diff --git a/src/lib/efreet_icon.h b/src/lib/efreet_icon.h new file mode 100644 index 0000000..e6454ad --- /dev/null +++ b/src/lib/efreet_icon.h @@ -0,0 +1,249 @@ +#ifndef EFREET_ICON_H +#define EFREET_ICON_H + +/** + * @file efreet_icon.h + * @brief Contains the structures and methods used to support the FDO icon + * theme specificiation. + * @addtogroup Efreet_Icon Efreet_Icon: The FDO Icon Theme + * Specification functions and structures + * + * @{ + */ + + +/** + * Event id for cache update. + */ +EAPI extern int EFREET_EVENT_ICON_CACHE_UPDATE; + +/** + * The possible contexts for an icon directory + */ +typedef enum Efreet_Icon_Theme_Context +{ + EFREET_ICON_THEME_CONTEXT_NONE, + EFREET_ICON_THEME_CONTEXT_ACTIONS, + EFREET_ICON_THEME_CONTEXT_DEVICES, + EFREET_ICON_THEME_CONTEXT_FILESYSTEMS, + EFREET_ICON_THEME_CONTEXT_MIMETYPES +} Efreet_Icon_Theme_Context; + +/** + * The possible size types for an icon directory + */ +typedef enum Efreet_Icon_Size_Type +{ + EFREET_ICON_SIZE_TYPE_NONE, + EFREET_ICON_SIZE_TYPE_FIXED, + EFREET_ICON_SIZE_TYPE_SCALABLE, + EFREET_ICON_SIZE_TYPE_THRESHOLD +} Efreet_Icon_Size_Type; + +/** + * Efreet_Icon_Theme + */ +typedef struct Efreet_Icon_Theme Efreet_Icon_Theme; + +/** + * Efreet_Icon_Theme + * @brief contains all of the known information about a given theme + */ +struct Efreet_Icon_Theme +{ + struct + { + const char *internal; /**< The internal theme name */ + const char *name; /**< The user visible name */ + } name; /**< The different names for the theme */ + + const char *comment; /**< String describing the theme */ + const char *example_icon; /**< Icon to use as an example of the theme */ + + /* An icon theme can have multiple directories that store it's icons. We + * need to be able to find a search each one. */ + + Eina_List *paths; /**< The paths */ + Eina_List *inherits; /**< Icon themes we inherit from */ + Eina_List *directories; /**< List of subdirectories for this theme */ +}; + +/** + * Efreet_Icon_Theme_Directory + */ +typedef struct Efreet_Icon_Theme_Directory Efreet_Icon_Theme_Directory; + +/** + * Efreet_Icon_Theme_Directory + * @brief Contains all the information about a sub-directory of a theme + */ +struct Efreet_Icon_Theme_Directory +{ + const char *name; /**< The directory name */ + Efreet_Icon_Theme_Context context; /**< The type of icons in the dir */ + Efreet_Icon_Size_Type type; /**< The size type for the icons */ + + struct + { + unsigned int normal; /**< The size for this directory */ + unsigned int min; /**< The minimum size for this directory */ + unsigned int max; /**< The maximum size for this directory */ + unsigned int threshold; /**< Size difference threshold */ + } size; /**< The size settings for the icon theme */ +}; + +/** + * Efreet_Icon + */ +typedef struct Efreet_Icon Efreet_Icon; + +/** + * Efreet_Icon + * @brief Contains all the information about a given icon + */ +struct Efreet_Icon +{ + const char *path; /**< Full path to the icon */ + const char *name; /**< Translated UTF8 string that can + be used for the icon name */ + + struct + { + int x0, /**< x0 position */ + y0, /**< y0 position */ + x1, /**< x1 position */ + y1; /**< y1 position */ + } embedded_text_rectangle; /**< Rectangle where text can + be displayed on the icon */ + + Eina_List *attach_points; /**< List of points to be used as anchor + points for emblems/overlays */ + + unsigned int ref_count; /**< References to this icon */ + unsigned char has_embedded_text_rectangle:1; /**< Has the embedded + rectangle set */ +}; + +/** + * Efreet_Icon_Point + */ +typedef struct Efreet_Icon_Point Efreet_Icon_Point; + +/** + * Efreet_Icon_Point + * @brief Stores an x, y point. + */ +struct Efreet_Icon_Point +{ + int x; /**< x coord */ + int y; /**< y coord */ +}; + +/** + * @return Returns the user icon directory + * @brief Returns the user icon directory + */ +EAPI const char *efreet_icon_user_dir_get(void); + +/** + * @return Returns the deprecated user icon directory + * @brief Returns the deprecated user icon directory + */ +EAPI const char *efreet_icon_deprecated_user_dir_get(void); + +/** + * @param ext The extension to add to the list of checked extensions + * @return Returns no value. + * @brief Adds the given extension to the list of possible icon extensions + */ +EAPI void efreet_icon_extension_add(const char *ext); + + +/** + * @return Returns a list of strings that are paths to other icon directories + * @brief Gets the list of all extra directories to look for icons. These + * directories are used to look for icons after looking in the user icon dir + * and before looking in standard system directories. The order of search is + * from first to last directory in this list. the strings in the list should + * be created with eina_stringshare_add(). + */ +EAPI Eina_List **efreet_icon_extra_list_get(void); + +/** + * @return Returns a list of strings that are icon extensions to look for + * @brief Gets the list of all icon extensions to look for + */ +EAPI Eina_List *efreet_icon_extensions_list_get(void); + +/** + * @return Returns a list of Efreet_Icon structs for all the non-hidden icon + * themes + * @brief Retrieves all of the non-hidden icon themes available on the system. + * The returned list must be freed. Do not free the list data. + */ +EAPI Eina_List *efreet_icon_theme_list_get(void); + +/** + * @param theme_name The theme to look for + * @return Returns the icon theme related to the given theme name or NULL if + * none exists. + * @brief Tries to get the icon theme structure for the given theme name + */ +EAPI Efreet_Icon_Theme *efreet_icon_theme_find(const char *theme_name); + +/** + * @param theme_name The icon theme to look for + * @param icon The icon to look for + * @param size The icon size to look for + * @return Returns the Efreet_Icon structure representing this icon or NULL + * if the icon is not found + * @brief Retrieves all of the information about the given icon. + */ +EAPI Efreet_Icon *efreet_icon_find(const char *theme_name, + const char *icon, + unsigned int size); + +/** + * @param theme_name The icon theme to look for + * @param icons List of icons to look for + * @param size; The icon size to look for + * @return Returns the path representing first found icon or + * NULL if none of the icons are found + * @brief Retrieves all of the information about the first found icon in + * the list. + * @note This function will search the given theme for all icons before falling + * back. This is useful when searching for mimetype icons. + * + * There is no guarantee for how long the pointer to the path will be valid. + * If the pointer is to be kept, the user must create a copy of the path. + */ +EAPI const char *efreet_icon_list_find(const char *theme_name, + Eina_List *icons, + unsigned int size); + +/** + * @param theme_name The icon theme to look for + * @param icon The icon to look for + * @param size; The icon size to look for + * @return Returns the path to the given icon or NULL if none found + * @brief Retrives the path to the given icon. + * + * There is no guarantee for how long the pointer to the path will be valid. + * If the pointer is to be kept, the user must create a copy of the path. + */ +EAPI const char *efreet_icon_path_find(const char *theme_name, + const char *icon, + unsigned int size); + +/** + * @param icon The Efreet_Icon to cleanup + * @return Returns no value. + * @brief Free's the given icon and all its internal data. + */ +EAPI void efreet_icon_free(Efreet_Icon *icon); + +/** + * @} + */ + +#endif diff --git a/src/lib/efreet_ini.c b/src/lib/efreet_ini.c new file mode 100644 index 0000000..375fda1 --- /dev/null +++ b/src/lib/efreet_ini.c @@ -0,0 +1,629 @@ +#ifdef HAVE_CONFIG_H +# include +#endif + +#undef alloca +#ifdef HAVE_ALLOCA_H +# include +#elif defined __GNUC__ +# define alloca __builtin_alloca +#elif defined _AIX +# define alloca __alloca +#elif defined _MSC_VER +# include +# define alloca _alloca +#else +# include +# ifdef __cplusplus +extern "C" +# endif +void *alloca (size_t); +#endif + +#include +#include +#include +#include + +#include + +/* define macros and variable for using the eina logging system */ +#define EFREET_MODULE_LOG_DOM _efreet_ini_log_dom +static int _efreet_ini_log_dom = -1; + +#include "Efreet.h" +#include "efreet_private.h" + +static Eina_Hash *efreet_ini_parse(const char *file); +static const char *efreet_ini_unescape(const char *str) EINA_ARG_NONNULL(1); +static Eina_Bool +efreet_ini_section_save(const Eina_Hash *hash, const void *key, void *data, void *fdata); +static Eina_Bool +efreet_ini_value_save(const Eina_Hash *hash, const void *key, void *data, void *fdata); + +/** + * @internal + * @return Returns > 0 on success or 0 on failure + * @brief Initialize the Ini parser subsystem + */ +int +efreet_ini_init(void) +{ + _efreet_ini_log_dom = eina_log_domain_register + ("efreet_ini", EFREET_DEFAULT_LOG_COLOR); + if (_efreet_ini_log_dom < 0) + { + EINA_LOG_ERR("Efreet: Could not create a log domain for efreet_ini"); + return 0; + } + return 1; +} + +/** + * @internal + * @returns the number of initializations left for this system + * @brief Attempts to shut down the subsystem if nothing else is using it + */ +void +efreet_ini_shutdown(void) +{ + eina_log_domain_unregister(_efreet_ini_log_dom); + _efreet_ini_log_dom = -1; +} + +EAPI Efreet_Ini * +efreet_ini_new(const char *file) +{ + Efreet_Ini *ini; + + ini = NEW(Efreet_Ini, 1); + if (!ini) return NULL; + + /* This can validly be NULL at the moment as _parse() will return NULL + * if the input file doesn't exist. Should we change _parse() to create + * the hash and only return NULL on failed parse? */ + ini->data = efreet_ini_parse(file); + + return ini; +} + +/** + * @internal + * @param file The file to parse + * @return Returns an Eina_Hash with the contents of @a file, or NULL if the + * file fails to parse or if the file doesn't exist + * @brief Parses the ini file @a file into an Eina_Hash + */ +static Eina_Hash * +efreet_ini_parse(const char *file) +{ + const char *buffer, *line_start; + FILE *f; + Eina_Hash *data, *section = NULL; + struct stat file_stat; + int line_length, left; + + if (!file) return NULL; + + f = fopen(file, "rb"); + if (!f) return NULL; + + if (fstat(fileno(f), &file_stat) || (file_stat.st_size < 1)) + { + fclose(f); + return NULL; + } + if (!S_ISREG(file_stat.st_mode)) /* if not a regular file - close */ + { + fclose(f); + return NULL; + } + + left = file_stat.st_size; + /* let's make mmap safe and just get 0 pages for IO erro */ + eina_mmap_safety_enabled_set(EINA_TRUE); + + buffer = mmap(NULL, left, PROT_READ, MAP_SHARED, fileno(f), 0); + if (buffer == MAP_FAILED) + { + fclose(f); + return NULL; + } + + data = eina_hash_string_small_new(EINA_FREE_CB(eina_hash_free)); + + line_start = buffer; + while (left > 0) + { + int sep; + + /* find the end of line */ + for (line_length = 0; + (line_length < left) && + (line_start[line_length] != '\n'); line_length++) + ; + + /* check for all white space */ + while (isspace(line_start[0]) && (line_length > 0)) + { + line_start++; + line_length--; + } + + /* skip empty lines and comments */ + if ((line_length == 0) || (line_start[0] == '\r') || + (line_start[0] == '\n') || (line_start[0] == '#') || + (line_start[0] == '\0')) + goto next_line; + + /* new section */ + if (line_start[0] == '[') + { + int header_length; + + /* find the ']' */ + for (header_length = 1; + (header_length < line_length) && + (line_start[header_length] != ']'); ++header_length) + ; + + if (line_start[header_length] == ']') + { + const char *header; + + header = alloca(header_length * sizeof(unsigned char)); + if (!header) goto next_line; + + memcpy((char*)header, line_start + 1, header_length - 1); + ((char*)header)[header_length - 1] = '\0'; + + section = eina_hash_string_small_new(EINA_FREE_CB(eina_stringshare_del)); + + eina_hash_del_by_key(data, header); +// if (old) INF("[efreet] Warning: duplicate section '%s' " + // "in file '%s'", header, file); + + eina_hash_add(data, header, section); + } + else + { + /* invalid file - skip line? or refuse to parse file? */ + /* just printf for now till we figure out what to do */ +// ERR("Invalid file (%s) (missing ] on group name)", file); + } + goto next_line; + } + + if (!section) + { + INF("Invalid file (%s) (missing section)", file); + goto error; + } + + /* find for '=' */ + for (sep = 0; (sep < line_length) && (line_start[sep] != '='); ++sep) + ; + + if (sep < line_length && line_start[sep] == '=') + { + char *key, *value; + int key_end, value_start, value_end; + + /* trim whitespace from end of key */ + for (key_end = sep - 1; + (key_end > 0) && isspace(line_start[key_end]); --key_end) + ; + + if (!isspace(line_start[key_end])) key_end++; + + /* trim whitespace from start of value */ + for (value_start = sep + 1; + (value_start < line_length) && + isspace(line_start[value_start]); ++value_start) + ; + + /* trim \n off of end of value */ + for (value_end = line_length; + (value_end > value_start) && + ((line_start[value_end] == '\n') || + (line_start[value_end] == '\r')); --value_end) + ; + + if (line_start[value_end] != '\n' + && line_start[value_end] != '\r' + && value_end < line_length) + value_end++; + + /* make sure we have a key. blank values are allowed */ + if (key_end <= 0) + { + /* invalid file... */ +// INF("Invalid file (%s) (invalid key=value pair)", file); + + goto next_line; + } + + key = alloca(key_end + 1); + value = alloca(value_end - value_start + 1); + if (!key || !value) goto next_line; + + memcpy(key, line_start, key_end); + key[key_end] = '\0'; + + memcpy(value, line_start + value_start, + value_end - value_start); + value[value_end - value_start] = '\0'; + + eina_hash_del_by_key(section, key); + eina_hash_add(section, key, efreet_ini_unescape(value)); + } + else + { + /* invalid file... */ + INF("Invalid file (%s) (missing = from key=value pair)", file); + goto error; + } + +next_line: + left -= line_length + 1; + line_start += line_length + 1; + } + munmap((char*) buffer, file_stat.st_size); + fclose(f); + +#if 0 + if (!eina_hash_population(data)) + { + eina_hash_free(data); + return NULL; + } +#endif + return data; +error: + if (data) eina_hash_free(data); + return NULL; +} + +EAPI void +efreet_ini_free(Efreet_Ini *ini) +{ + if (!ini) return; + + IF_FREE_HASH(ini->data); + FREE(ini); +} + +EAPI int +efreet_ini_save(Efreet_Ini *ini, const char *file) +{ + char *dir; + FILE *f; + + EINA_SAFETY_ON_NULL_RETURN_VAL(ini, 0); + EINA_SAFETY_ON_NULL_RETURN_VAL(ini->data, 0); + EINA_SAFETY_ON_NULL_RETURN_VAL(file, 0); + + dir = ecore_file_dir_get(file); + if (!ecore_file_mkpath(dir)) + { + free(dir); + return 0; + } + free(dir); + f = fopen(file, "wb"); + if (!f) return 0; + eina_hash_foreach(ini->data, efreet_ini_section_save, f); + fclose(f); + + return 1; +} + +EAPI int +efreet_ini_section_set(Efreet_Ini *ini, const char *section) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(ini, 0); + EINA_SAFETY_ON_NULL_RETURN_VAL(ini->data, 0); + EINA_SAFETY_ON_NULL_RETURN_VAL(section, 0); + + ini->section = eina_hash_find(ini->data, section); + return (ini->section ? 1 : 0); +} + +EAPI void +efreet_ini_section_add(Efreet_Ini *ini, const char *section) +{ + Eina_Hash *hash; + + EINA_SAFETY_ON_NULL_RETURN(ini); + EINA_SAFETY_ON_NULL_RETURN(section); + + if (!ini->data) + ini->data = eina_hash_string_small_new(EINA_FREE_CB(eina_hash_free)); + if (eina_hash_find(ini->data, section)) return; + + hash = eina_hash_string_small_new(EINA_FREE_CB(eina_stringshare_del)); + eina_hash_add(ini->data, section, hash); +} + +EAPI const char * +efreet_ini_string_get(Efreet_Ini *ini, const char *key) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(ini, NULL); + EINA_SAFETY_ON_NULL_RETURN_VAL(ini->section, NULL); + EINA_SAFETY_ON_NULL_RETURN_VAL(key, NULL); + + return eina_hash_find(ini->section, key); +} + +EAPI void +efreet_ini_string_set(Efreet_Ini *ini, const char *key, const char *value) +{ + EINA_SAFETY_ON_NULL_RETURN(ini); + EINA_SAFETY_ON_NULL_RETURN(ini->section); + EINA_SAFETY_ON_NULL_RETURN(key); + + eina_hash_del_by_key(ini->section, key); + eina_hash_add(ini->section, key, eina_stringshare_add(value)); +} + +EAPI int +efreet_ini_int_get(Efreet_Ini *ini, const char *key) +{ + const char *str; + + EINA_SAFETY_ON_NULL_RETURN_VAL(ini, -1); + EINA_SAFETY_ON_NULL_RETURN_VAL(ini->section, -1); + EINA_SAFETY_ON_NULL_RETURN_VAL(key, -1); + + str = efreet_ini_string_get(ini, key); + if (str) return atoi(str); + + return -1; +} + +EAPI void +efreet_ini_int_set(Efreet_Ini *ini, const char *key, int value) +{ + char str[12]; + + EINA_SAFETY_ON_NULL_RETURN(ini); + EINA_SAFETY_ON_NULL_RETURN(ini->section); + EINA_SAFETY_ON_NULL_RETURN(key); + + snprintf(str, 12, "%d", value); + efreet_ini_string_set(ini, key, str); +} + +EAPI double +efreet_ini_double_get(Efreet_Ini *ini, const char *key) +{ + const char *str; + + EINA_SAFETY_ON_NULL_RETURN_VAL(ini, -1); + EINA_SAFETY_ON_NULL_RETURN_VAL(ini->section, -1); + EINA_SAFETY_ON_NULL_RETURN_VAL(key, -1); + + str = efreet_ini_string_get(ini, key); + if (str) return atof(str); + + return -1; +} + +EAPI void +efreet_ini_double_set(Efreet_Ini *ini, const char *key, double value) +{ + char str[512]; + size_t len; + + EINA_SAFETY_ON_NULL_RETURN(ini); + EINA_SAFETY_ON_NULL_RETURN(ini->section); + EINA_SAFETY_ON_NULL_RETURN(key); + + snprintf(str, 512, "%.6f", value); + len = strlen(str) - 1; + /* Strip trailing zero's */ + while (str[len] == '0' && str[len - 1] != '.') str[len--] = '\0'; + efreet_ini_string_set(ini, key, str); +} + +EAPI unsigned int +efreet_ini_boolean_get(Efreet_Ini *ini, const char *key) +{ + const char *str; + + EINA_SAFETY_ON_NULL_RETURN_VAL(ini, 0); + EINA_SAFETY_ON_NULL_RETURN_VAL(ini->section, 0); + EINA_SAFETY_ON_NULL_RETURN_VAL(key, 0); + + str = efreet_ini_string_get(ini, key); + if (str && !strcmp("true", str)) return 1; + + return 0; +} + +EAPI void +efreet_ini_boolean_set(Efreet_Ini *ini, const char *key, unsigned int value) +{ + EINA_SAFETY_ON_NULL_RETURN(ini); + EINA_SAFETY_ON_NULL_RETURN(ini->section); + EINA_SAFETY_ON_NULL_RETURN(key); + + if (value) efreet_ini_string_set(ini, key, "true"); + else efreet_ini_string_set(ini, key, "false"); +} + +EAPI const char * +efreet_ini_localestring_get(Efreet_Ini *ini, const char *key) +{ + const char *lang, *country, *modifier; + const char *val = NULL; + char *buf; + int maxlen = 5; /* _, @, [, ] and \0 */ + int found = 0; + + EINA_SAFETY_ON_NULL_RETURN_VAL(ini, NULL); + EINA_SAFETY_ON_NULL_RETURN_VAL(ini->section, NULL); + EINA_SAFETY_ON_NULL_RETURN_VAL(key, NULL); + + lang = efreet_lang_get(); + country = efreet_lang_country_get(); + modifier = efreet_lang_modifier_get(); + + maxlen += strlen(key); + if (lang) maxlen += strlen(lang); + if (country) maxlen += strlen(country); + if (modifier) maxlen += strlen(modifier); + + buf = alloca(maxlen); + + if (lang && modifier && country) + { + snprintf(buf, maxlen, "%s[%s_%s@%s]", key, lang, country, modifier); + val = efreet_ini_string_get(ini, buf); + if (val && (*val != '\0')) found = 1; + } + + if (!found && lang && country) + { + snprintf(buf, maxlen, "%s[%s_%s]", key, lang, country); + val = efreet_ini_string_get(ini, buf); + if (val && (*val != '\0')) found = 1; + } + + if (!found && lang && modifier) + { + snprintf(buf, maxlen, "%s[%s@%s]", key, lang, modifier); + val = efreet_ini_string_get(ini, buf); + if (val && (*val != '\0')) found = 1; + } + + if (!found && lang) + { + snprintf(buf, maxlen, "%s[%s]", key, lang); + val = efreet_ini_string_get(ini, buf); + if (val && (*val != '\0')) found = 1; + } + + if (!found) + val = efreet_ini_string_get(ini, key); + + return val; +} + +EAPI void +efreet_ini_localestring_set(Efreet_Ini *ini, const char *key, const char *value) +{ + const char *lang, *country, *modifier; + char *buf; + int maxlen = 5; /* _, @, [, ] and \0 */ + + EINA_SAFETY_ON_NULL_RETURN(ini); + EINA_SAFETY_ON_NULL_RETURN(ini->section); + EINA_SAFETY_ON_NULL_RETURN(key); + + lang = efreet_lang_get(); + country = efreet_lang_country_get(); + modifier = efreet_lang_modifier_get(); + + maxlen += strlen(key); + if (lang) maxlen += strlen(lang); + if (country) maxlen += strlen(country); + if (modifier) maxlen += strlen(modifier); + + buf = alloca(maxlen); + + if (lang && modifier && country) + snprintf(buf, maxlen, "%s[%s_%s@%s]", key, lang, country, modifier); + else if (lang && country) + snprintf(buf, maxlen, "%s[%s_%s]", key, lang, country); + else if (lang && modifier) + snprintf(buf, maxlen, "%s[%s@%s]", key, lang, modifier); + else if (lang) + snprintf(buf, maxlen, "%s[%s]", key, lang); + else + return; + + efreet_ini_string_set(ini, buf, value); +} + +EAPI void +efreet_ini_key_unset(Efreet_Ini *ini, const char *key) +{ + EINA_SAFETY_ON_NULL_RETURN(ini); + EINA_SAFETY_ON_NULL_RETURN(ini->section); + EINA_SAFETY_ON_NULL_RETURN(key); + + eina_hash_del_by_key(ini->section, key); +} + +/** + * @param str The string to unescape + * @return An allocated unescaped string + * @brief Unescapes backslash escapes in a string + */ +static const char * +efreet_ini_unescape(const char *str) +{ + char *buf, *dest; + const char *p; + + if (!strchr(str, '\\')) return eina_stringshare_add(str); + buf = alloca(strlen(str) + 1); + + p = str; + dest = buf; + while (*p) + { + if ((*p == '\\') && (p[1] != '\0')) + { + p++; + switch (*p) + { + case 's': + *(dest++) = ' '; + break; + case 'n': + *(dest++) = '\n'; + break; + case 't': + *(dest++) = '\t'; + break; + case 'r': + *(dest++) = '\r'; + break; + case '\\': + *(dest++) = '\\'; + break; + default: + (*dest++) = '\\'; + (*dest++) = *p; + } + } + else + *(dest++) = *p; + + p++; + } + + *(dest) = '\0'; + return eina_stringshare_add(buf); +} + +static Eina_Bool +efreet_ini_section_save(const Eina_Hash *hash __UNUSED__, const void *key, void *value, void *fdata) +{ + FILE *f = fdata; + + fprintf(f, "[%s]\n", (char *)key); + eina_hash_foreach(value, efreet_ini_value_save, f); + return EINA_TRUE; +} + +static Eina_Bool +efreet_ini_value_save(const Eina_Hash *hash __UNUSED__, const void *key, void *value, void *fdata) +{ + FILE *f = fdata; + + fprintf(f, "%s=%s\n", (char *)key, (char *)value); + return EINA_TRUE; +} diff --git a/src/lib/efreet_ini.h b/src/lib/efreet_ini.h new file mode 100644 index 0000000..f5b8a9e --- /dev/null +++ b/src/lib/efreet_ini.h @@ -0,0 +1,181 @@ +#ifndef EFREET_INI_H +#define EFREET_INI_H + +/** + * @internal + * @file efreet_ini.h + * @brief A simple and fast INI parser + * @addtogroup Efreet_Ini Efreet_Ini: An INI parser + * + * @{ + */ + +/** + * Efreet_Ini + */ +typedef struct Efreet_Ini Efreet_Ini; + +/** + * Efreet_Ini + * @brief Contains all the information about an ini file. + */ +struct Efreet_Ini +{ + Eina_Hash *data; /**< Hash of string => (Hash of string => string) */ + Eina_Hash *section; /**< currently selected section */ +}; + + +/** + * @param file The file to parse + * @return Returns a new Efreet_Ini structure initialized with the contents + * of @a file, or NULL on memory allocation failure + * @brief Creates and initializes a new Ini structure with the contents of + * @a file, or NULL on failure + */ +EAPI Efreet_Ini *efreet_ini_new(const char *file); + +/** + * @param ini The Efreet_Ini to work with + * @return Returns no value + * @brief Frees the given Efree_Ini structure. + */ +EAPI void efreet_ini_free(Efreet_Ini *ini); + +/** + * @param ini The Efreet_Ini to work with + * @param file The file to load + * @return Returns no value + * @brief Saves the given Efree_Ini structure. + */ +EAPI int efreet_ini_save(Efreet_Ini *ini, const char *path); + + +/** + * @param ini The Efreet_Ini to work with + * @param section The section of the ini file we want to get values from + * @return Returns 1 if the section exists, otherwise 0 + * @brief Sets the current working section of the ini file to @a section + */ +EAPI int efreet_ini_section_set(Efreet_Ini *ini, const char *section); + +/** + * @param ini The Efreet_Ini to work with + * @param section The section of the ini file we want to add + * @return Returns no value + * @brief Adds a new working section of the ini file to @a section + */ +EAPI void efreet_ini_section_add(Efreet_Ini *ini, const char *section); + + +/** + * @param ini The Efree_Ini to work with + * @param key The key to lookup + * @return Returns the string associated with the given key or NULL if not + * found. + * @brief Retrieves the value for the given key or NULL if none found. + */ +EAPI const char *efreet_ini_string_get(Efreet_Ini *ini, const char *key); + +/** + * @param ini The Efree_Ini to work with + * @param key The key to use + * @param value The value to set + * @return Returns no value + * @brief Sets the value for the given key + */ +EAPI void efreet_ini_string_set(Efreet_Ini *ini, const char *key, + const char *value); + + +/** + * @param ini The ini struct to work with + * @param key The key to search for + * @return Returns the utf8 encoded string associated with @a key, or NULL + * if none found + * @brief Retrieves the utf8 encoded string associated with @a key in the current locale or NULL if none found + */ +EAPI const char *efreet_ini_localestring_get(Efreet_Ini *ini, const char *key); + +/** + * @param ini The ini struct to work with + * @param key The key to use + * @param value The value to set + * @return Returns no value + * @brief Sets the value for the given key + */ +EAPI void efreet_ini_localestring_set(Efreet_Ini *ini, const char *key, + const char *value); + + +/** + * @param ini The ini struct to work with + * @param key The key to search for + * @return Returns 1 if the boolean is true, 0 otherwise + * @brief Retrieves the boolean value at key @a key from the ini @a ini + */ +EAPI unsigned int efreet_ini_boolean_get(Efreet_Ini *ini, const char *key); + +/** + * @param ini The ini struct to work with + * @param key The key to use + * @param value The value to set + * @return Returns no value + * @brief Sets the value for the given key + */ +EAPI void efreet_ini_boolean_set(Efreet_Ini *ini, const char *key, + unsigned int value); + + +/** + * @param ini The Efree_Ini to work with + * @param key The key to lookup + * @return Returns the integer associated with the given key or -1 if not + * found. + * @brief Retrieves the value for the given key or -1 if none found. + */ +EAPI int efreet_ini_int_get(Efreet_Ini *ini, const char *key); + +/** + * @param ini The Efree_Ini to work with + * @param key The key to use + * @param value The value to set + * @return Returns no value + * @brief Sets the value for the given key + */ +EAPI void efreet_ini_int_set(Efreet_Ini *ini, const char *key, int value); + + +/** + * @param ini The Efree_Ini to work with + * @param key The key to lookup + * @return Returns the double associated with the given key or -1 if not + * found. + * @brief Retrieves the value for the given key or -1 if none found. + */ +EAPI double efreet_ini_double_get(Efreet_Ini *ini, const char *key); + +/** + * @param ini The Efree_Ini to work with + * @param key The key to use + * @param value The value to set + * @return Returns no value + * @brief Sets the value for the given key + */ +EAPI void efreet_ini_double_set(Efreet_Ini *ini, const char *key, + double value); + + +/** + * @param ini The ini struct to work with + * @param key The key to remove + * @return Returns no value + * @brief Remove the given key from the ini struct + */ +EAPI void efreet_ini_key_unset(Efreet_Ini *ini, const char *key); + +/** + * @} + */ + +#endif diff --git a/src/lib/efreet_menu.c b/src/lib/efreet_menu.c new file mode 100644 index 0000000..e612ba1 --- /dev/null +++ b/src/lib/efreet_menu.c @@ -0,0 +1,3887 @@ +#ifdef HAVE_CONFIG_H +# include +#endif + +#undef alloca +#ifdef HAVE_ALLOCA_H +# include +#elif defined __GNUC__ +# define alloca __builtin_alloca +#elif defined _AIX +# define alloca __alloca +#elif defined _MSC_VER +# include +# define alloca _alloca +#else +# include +# ifdef __cplusplus +extern "C" +# endif +void *alloca (size_t); +#endif + +#include + +/* define macros and variable for using the eina logging system */ +#define EFREET_MODULE_LOG_DOM _efreet_menu_log_dom +static int _efreet_menu_log_dom = -1; + +#include "Efreet.h" +#include "efreet_private.h" +#include "efreet_xml.h" + +typedef struct Efreet_Menu_Move Efreet_Menu_Move; + +struct Efreet_Menu_Move +{ + const char *old_name; /**< The menu path to move from */ + const char *new_name; /**< The menu path to move too */ +}; + +typedef struct Efreet_Menu_Internal Efreet_Menu_Internal; + +struct Efreet_Menu_Internal +{ + struct + { + const char *path; /**< The base file path */ + const char *name; /**< The filename for this menu */ + } file; /**< The menu file information */ + + struct + { + const char *internal; /**< The menu name */ + const char *name; /**< Name to use in the menus */ + } name; /**< The names for this menu */ + + Efreet_Desktop *directory; /**< The directory */ + Eina_List *directories; /**< All the directories set in the menu file */ + + Efreet_Menu_Move *current_move; /**< The current move */ + + Eina_List *app_dirs; /**< .desktop application directories */ + + Eina_List *app_pool; /**< application pool */ + Eina_List *applications; /**< applications in this menu */ + + Eina_List *directory_dirs; /**< .directory file directories */ + Eina_Hash *directory_cache; /**< .directory dirs */ + + Eina_List *moves; /**< List of moves to be handled by the menu */ + Eina_List *filters; /**< Include and Exclude filters */ + + Efreet_Menu_Internal *parent; /**< Our parent menu */ + Eina_List *sub_menus; /**< Our sub menus */ + + Eina_List *layout; /**< This menus layout */ + Eina_List *default_layout; /**< Default layout */ + signed char show_empty; /**< Whether to show empty menus */ + signed char in_line; /**< Whether this meny can be inlined */ + signed char inline_limit; /**< Number of elements which triggers inline */ + signed char inline_header; /**< Whether we should use the header name when this menu is inlined */ + signed char inline_alias; /**< Whether we should use the menu name when inlining */ + + unsigned char seen_allocated:1; /**< have we set the only_unallocated */ + unsigned char only_unallocated:1; /**< Show only unallocated .desktops */ + + unsigned char seen_deleted:1; /**< Have we seen the deleted item yet */ + unsigned char deleted:1; /**< The menu is deleted */ +}; + +typedef struct Efreet_Menu_App_Dir Efreet_Menu_App_Dir; + +struct Efreet_Menu_App_Dir +{ + const char *path; /**< directory path */ + const char *prefix; /**< If it's legacy it can have a prefix */ + unsigned int legacy:1; /**< is this a legacy dir */ +}; + +enum Efreet_Menu_Filter_Op_Type +{ + EFREET_MENU_FILTER_OP_OR, + EFREET_MENU_FILTER_OP_AND, + EFREET_MENU_FILTER_OP_NOT +}; + +typedef enum Efreet_Menu_Filter_Op_Type Efreet_Menu_Filter_Op_Type; + +enum Efreet_Menu_Filter_Type +{ + EFREET_MENU_FILTER_INCLUDE, + EFREET_MENU_FILTER_EXCLUDE +}; + +typedef enum Efreet_Menu_Filter_Type Efreet_Menu_Filter_Type; + +typedef struct Efreet_Menu_Filter_Op Efreet_Menu_Filter_Op; + +struct Efreet_Menu_Filter_Op +{ + Efreet_Menu_Filter_Op_Type type; /**< The type of operation */ + Eina_List *categories; /**< The categories this op applies too */ + Eina_List *filenames; /**< The filenames this op applies too */ + + Eina_List *filters; /**< Child filters */ + + unsigned char all:1; /**< Applies to all .desktop files */ +}; + +typedef struct Efreet_Menu_Filter Efreet_Menu_Filter; + +struct Efreet_Menu_Filter +{ + Efreet_Menu_Filter_Type type; /**< The type of filter */ + Efreet_Menu_Filter_Op *op; /**< The filter operations */ +}; + +enum Efreet_Menu_Layout_Type +{ + EFREET_MENU_LAYOUT_MENUNAME, + EFREET_MENU_LAYOUT_FILENAME, + EFREET_MENU_LAYOUT_SEPARATOR, + EFREET_MENU_LAYOUT_MERGE +}; + +typedef enum Efreet_Menu_Layout_Type Efreet_Menu_Layout_Type; + +typedef struct Efreet_Menu_Layout Efreet_Menu_Layout; + +struct Efreet_Menu_Layout +{ + Efreet_Menu_Layout_Type type; /**< The type of layout */ + const char *name; /**< The name of the element */ + + /* The items below are for Menuname Layout elements */ + signed char show_empty; /**< Whether to show empty menus */ + signed char in_line; /**< Whether this meny can be inlined */ + signed char inline_limit; /**< Number of elements which triggers inline */ + signed char inline_header; /**< Whether we should use the header name when this menu is inlined */ + signed char inline_alias; /**< Whether we should use the menu name when inlining */ +}; + +typedef struct Efreet_Menu_Desktop Efreet_Menu_Desktop; + +struct Efreet_Menu_Desktop +{ + Efreet_Desktop *desktop; /**< The desktop we refer too */ + const char *id; /**< The desktop file id */ + unsigned char allocated:1; /**< If this desktop has been allocated */ +}; + +static const char *efreet_menu_prefix = NULL; /**< The $XDG_MENU_PREFIX env var */ +Eina_List *efreet_menu_kde_legacy_dirs = NULL; /**< The directories to use for KDELegacy entries */ +static const char *efreet_tag_menu = NULL; +static const char *efreet_menu_file = NULL; /**< A menu file set explicityl as default */ + +static Eina_Hash *efreet_merged_menus = NULL; +static Eina_Hash *efreet_merged_dirs = NULL; + +static Eina_Hash *efreet_menu_handle_cbs = NULL; +static Eina_Hash *efreet_menu_filter_cbs = NULL; +static Eina_Hash *efreet_menu_move_cbs = NULL; +static Eina_Hash *efreet_menu_layout_cbs = NULL; + +static const char *efreet_menu_prefix_get(void); + +static Efreet_Menu_Internal *efreet_menu_by_name_find(Efreet_Menu_Internal *internal, + const char *name, + Efreet_Menu_Internal **parent); +static int efreet_menu_cb_compare_names(Efreet_Menu_Internal *internal, const char *name); +static int efreet_menu_cb_md_compare_ids(Efreet_Menu_Desktop *md, const char *name); + +static int efreet_menu_cb_entry_compare_menu(Efreet_Menu *entry, Efreet_Menu_Internal *internal); +static int efreet_menu_cb_entry_compare_desktop(Efreet_Menu *entry, Efreet_Desktop *desktop); + +static int efreet_menu_cb_move_compare(Efreet_Menu_Move *move, const char *old); + +static int efreet_menu_process(Efreet_Menu_Internal *internal, unsigned int only_unallocated); +static int efreet_menu_process_dirs(Efreet_Menu_Internal *internal); +static int efreet_menu_app_dirs_process(Efreet_Menu_Internal *internal); +static int efreet_menu_app_dir_scan(Efreet_Menu_Internal *internal, + const char *path, + const char *id, + int legacy); +static int efreet_menu_directory_dirs_process(Efreet_Menu_Internal *internal); +static int efreet_menu_directory_dir_scan(const char *path, + const char *relative_path, + Eina_Hash *cache); +static Efreet_Desktop *efreet_menu_directory_get(Efreet_Menu_Internal *internal, + const char *path); +static void efreet_menu_process_filters(Efreet_Menu_Internal *internal, + unsigned int only_unallocated); +static Eina_List *efreet_menu_process_app_pool(Eina_List *pool, + Eina_List *applications, + Eina_Hash *matches, + Efreet_Menu_Filter *filter, + unsigned int only_unallocated); +static int efreet_menu_filter_matches(Efreet_Menu_Filter_Op *op, + Efreet_Menu_Desktop *md); +static int efreet_menu_filter_or_matches(Efreet_Menu_Filter_Op *op, + Efreet_Menu_Desktop *md); +static int efreet_menu_filter_and_matches(Efreet_Menu_Filter_Op *op, + Efreet_Menu_Desktop *md); +static int efreet_menu_filter_not_matches(Efreet_Menu_Filter_Op *op, + Efreet_Menu_Desktop *md); + +static Efreet_Menu *efreet_menu_layout_menu(Efreet_Menu_Internal *internal); +static Efreet_Menu *efreet_menu_layout_desktop(Efreet_Menu_Desktop *md); +static void efreet_menu_layout_entries_get(Efreet_Menu *entry, Efreet_Menu_Internal *internal, + Efreet_Menu_Layout *layout); +static int efreet_menu_layout_is_empty(Efreet_Menu *entry); + +static Efreet_Menu_Internal *efreet_menu_internal_new(void); +static void efreet_menu_internal_free(Efreet_Menu_Internal *internal); +static void efreet_menu_create_sub_menu_list(Efreet_Menu_Internal *internal); +static void efreet_menu_create_app_dirs_list(Efreet_Menu_Internal *internal); +static void efreet_menu_create_directory_dirs_list(Efreet_Menu_Internal *internal); +static void efreet_menu_create_directories_list(Efreet_Menu_Internal *internal); +static void efreet_menu_create_move_list(Efreet_Menu_Internal *internal); +static void efreet_menu_create_filter_list(Efreet_Menu_Internal *internal); +static void efreet_menu_create_layout_list(Efreet_Menu_Internal *internal); +static void efreet_menu_create_default_layout_list(Efreet_Menu_Internal *internal); +static const char *efreet_menu_path_get(Efreet_Menu_Internal *internal, const char *suffix); + +static Efreet_Menu_App_Dir *efreet_menu_app_dir_new(void); +static void efreet_menu_app_dir_free(Efreet_Menu_App_Dir *dir); + +static Efreet_Menu_Move *efreet_menu_move_new(void); +static void efreet_menu_move_free(Efreet_Menu_Move *move); + +static Efreet_Menu_Filter *efreet_menu_filter_new(void); +static void efreet_menu_filter_free(Efreet_Menu_Filter *filter); + +static Efreet_Menu_Layout *efreet_menu_layout_new(void); +static void efreet_menu_layout_free(Efreet_Menu_Layout *layout); + +static Efreet_Menu_Filter_Op *efreet_menu_filter_op_new(void); +static void efreet_menu_filter_op_free(Efreet_Menu_Filter_Op *op); + +static Efreet_Menu_Desktop *efreet_menu_desktop_new(void); +static void efreet_menu_desktop_free(Efreet_Menu_Desktop *md); + +static Efreet_Menu *efreet_menu_entry_new(void); + +static int efreet_menu_handle_menu(Efreet_Menu_Internal *internal, Efreet_Xml *xml); +static int efreet_menu_handle_name(Efreet_Menu_Internal *parent, Efreet_Xml *xml); + +static int efreet_menu_handle_sub_menu(Efreet_Menu_Internal *parent, Efreet_Xml *xml); +static int efreet_menu_handle_app_dir(Efreet_Menu_Internal *parent, Efreet_Xml *xml); +static int efreet_menu_handle_default_app_dirs(Efreet_Menu_Internal *parent, Efreet_Xml *xml); +static int efreet_menu_handle_directory_dir(Efreet_Menu_Internal *parent, Efreet_Xml *xml); +static int efreet_menu_handle_default_directory_dirs(Efreet_Menu_Internal *parent, Efreet_Xml *xml); +static int efreet_menu_handle_directory(Efreet_Menu_Internal *parent, Efreet_Xml *xml); +static int efreet_menu_handle_only_unallocated(Efreet_Menu_Internal *parent, Efreet_Xml *xml); +static int efreet_menu_handle_not_only_unallocated(Efreet_Menu_Internal *parent, Efreet_Xml *xml); +static int efreet_menu_handle_deleted(Efreet_Menu_Internal *parent, Efreet_Xml *xml); +static int efreet_menu_handle_not_deleted(Efreet_Menu_Internal *parent, Efreet_Xml *xml); +static int efreet_menu_handle_include(Efreet_Menu_Internal *parent, Efreet_Xml *xml); +static int efreet_menu_handle_exclude(Efreet_Menu_Internal *parent, Efreet_Xml *xml); +static int efreet_menu_handle_filename(Efreet_Menu_Filter_Op *op, Efreet_Xml *xml); +static int efreet_menu_handle_category(Efreet_Menu_Filter_Op *op, Efreet_Xml *xml); +static int efreet_menu_handle_all(Efreet_Menu_Filter_Op *op, Efreet_Xml *xml); +static int efreet_menu_handle_and(Efreet_Menu_Filter_Op *op, Efreet_Xml *xml); +static int efreet_menu_handle_or(Efreet_Menu_Filter_Op *op, Efreet_Xml *xml); +static int efreet_menu_handle_not(Efreet_Menu_Filter_Op *op, Efreet_Xml *xml); +static int efreet_menu_handle_merge_file(Efreet_Menu_Internal *parent, Efreet_Xml *xml); +static int efreet_menu_handle_merge_dir(Efreet_Menu_Internal *parent, Efreet_Xml *xml); +static int efreet_menu_handle_default_merge_dirs(Efreet_Menu_Internal *parent, Efreet_Xml *xml); +static int efreet_menu_handle_legacy_dir(Efreet_Menu_Internal *parent, Efreet_Xml *xml); +static Efreet_Menu_Internal *efreet_menu_handle_legacy_dir_helper(Efreet_Menu_Internal *root, + Efreet_Menu_Internal *parent, + const char *legacy_dir, + const char *prefix); +static int efreet_menu_handle_kde_legacy_dirs(Efreet_Menu_Internal *parent, Efreet_Xml *xml); +static int efreet_menu_handle_move(Efreet_Menu_Internal *parent, Efreet_Xml *xml); +static int efreet_menu_handle_old(Efreet_Menu_Internal *parent, Efreet_Xml *xml); +static int efreet_menu_handle_new(Efreet_Menu_Internal *parent, Efreet_Xml *xml); +static int efreet_menu_handle_layout(Efreet_Menu_Internal *parent, Efreet_Xml *xml); +static int efreet_menu_handle_default_layout(Efreet_Menu_Internal *parent, Efreet_Xml *xml); + +static int efreet_menu_handle_filter(Efreet_Menu_Internal *parent, Efreet_Xml *xml, + Efreet_Menu_Filter_Type type); +static int efreet_menu_handle_filter_op(Efreet_Menu_Filter_Op *op, Efreet_Xml *xml); +static int efreet_menu_handle_filter_child_op(Efreet_Menu_Filter_Op *op, Efreet_Xml *xml, + Efreet_Menu_Filter_Op_Type type); + +static int efreet_menu_handle_layout_menuname(Efreet_Menu_Internal *parent, Efreet_Xml *xml, int def); +static int efreet_menu_handle_layout_filename(Efreet_Menu_Internal *parent, Efreet_Xml *xml, int def); +static int efreet_menu_handle_layout_separator(Efreet_Menu_Internal *parent, Efreet_Xml *xml, int def); +static int efreet_menu_handle_layout_merge(Efreet_Menu_Internal *parent, Efreet_Xml *xml, int def); + +static int efreet_menu_merge(Efreet_Menu_Internal *parent, Efreet_Xml *xml, const char *path); +static int efreet_menu_merge_dir(Efreet_Menu_Internal *parent, Efreet_Xml *xml, const char *path); + +static int efreet_menu_cb_app_dirs_compare(Efreet_Menu_App_Dir *a, const char *b); + +static void efreet_menu_resolve_moves(Efreet_Menu_Internal *internal); +static void efreet_menu_concatenate(Efreet_Menu_Internal *dest, Efreet_Menu_Internal *src); + +static int efreet_menu_cb_menu_compare(Efreet_Menu_Internal *a, Efreet_Menu_Internal *b); +static int efreet_menu_cb_md_compare(const Efreet_Menu_Desktop *a, const Efreet_Menu_Desktop *b); + +static int efreet_menu_save_menu(Efreet_Menu *menu, FILE *f, int indent); +static int efreet_menu_save_indent(FILE *f, int indent); + +static void efreet_menu_path_set(Efreet_Menu_Internal *internal, const char *path); + +int +efreet_menu_init(void) +{ + int i; + + struct + { + const char *key; + int (*cb)(Efreet_Menu_Internal *parent, Efreet_Xml *xml); + } menu_cbs[] = { + {"Menu", efreet_menu_handle_sub_menu}, + {"AppDir", efreet_menu_handle_app_dir}, + {"DefaultAppDirs", efreet_menu_handle_default_app_dirs}, + {"DirectoryDir", efreet_menu_handle_directory_dir}, + {"DefaultDirectoryDirs", efreet_menu_handle_default_directory_dirs}, + {"Name", efreet_menu_handle_name}, + {"Directory", efreet_menu_handle_directory}, + {"OnlyUnallocated", efreet_menu_handle_only_unallocated}, + {"NotOnlyUnallocated", efreet_menu_handle_not_only_unallocated}, + {"Deleted", efreet_menu_handle_deleted}, + {"NotDeleted", efreet_menu_handle_not_deleted}, + {"Include", efreet_menu_handle_include}, + {"Exclude", efreet_menu_handle_exclude}, + {"MergeFile", efreet_menu_handle_merge_file}, + {"MergeDir", efreet_menu_handle_merge_dir}, + {"DefaultMergeDirs", efreet_menu_handle_default_merge_dirs}, + {"LegacyDir", efreet_menu_handle_legacy_dir}, + {"KDELegacyDirs", efreet_menu_handle_kde_legacy_dirs}, + {"Move", efreet_menu_handle_move}, + {"Layout", efreet_menu_handle_layout}, + {"DefaultLayout", efreet_menu_handle_default_layout}, + {NULL, NULL} + }; + + struct + { + const char *key; + int (*cb)(Efreet_Menu_Filter_Op *op, Efreet_Xml *xml); + } filter_cbs[] = { + {"Filename", efreet_menu_handle_filename}, + {"Category", efreet_menu_handle_category}, + {"All", efreet_menu_handle_all}, + {"And", efreet_menu_handle_and}, + {"Or", efreet_menu_handle_or}, + {"Not", efreet_menu_handle_not}, + {NULL, NULL} + }; + + struct + { + const char *key; + int (*cb)(Efreet_Menu_Internal *parent, Efreet_Xml *xml); + } move_cbs[] = { + {"Old", efreet_menu_handle_old}, + {"New", efreet_menu_handle_new}, + {NULL, NULL} + }; + + struct + { + const char *key; + int (*cb)(Efreet_Menu_Internal *parent, Efreet_Xml *xml, int def); + } layout_cbs[] = { + {"Menuname", efreet_menu_handle_layout_menuname}, + {"Filename", efreet_menu_handle_layout_filename}, + {"Separator", efreet_menu_handle_layout_separator}, + {"Merge", efreet_menu_handle_layout_merge}, + {NULL, NULL} + }; + + _efreet_menu_log_dom = eina_log_domain_register + ("efreet_menu", EFREET_DEFAULT_LOG_COLOR); + if (_efreet_menu_log_dom < 0) + { + EINA_LOG_ERR("Efreet: Could not create a log domain for efreet_menu"); + return 0; + } + + efreet_menu_handle_cbs = eina_hash_string_superfast_new(NULL); + efreet_menu_filter_cbs = eina_hash_string_superfast_new(NULL); + efreet_menu_move_cbs = eina_hash_string_superfast_new(NULL); + efreet_menu_layout_cbs = eina_hash_string_superfast_new(NULL); + if (!efreet_menu_handle_cbs || !efreet_menu_filter_cbs + || !efreet_menu_move_cbs || !efreet_menu_layout_cbs) + { + eina_log_domain_unregister(_efreet_menu_log_dom); + _efreet_menu_log_dom = -1; + return 0; + } + + /* set Menu into it's own so we can check the XML is valid before trying + * to handle it */ + efreet_tag_menu = eina_stringshare_add(menu_cbs[0].key); + + for (i = 0; menu_cbs[i].key; i++) + { + eina_hash_del(efreet_menu_handle_cbs, + menu_cbs[i].key, + NULL); + eina_hash_add(efreet_menu_handle_cbs, + menu_cbs[i].key, + menu_cbs[i].cb); + } + for (i = 0; filter_cbs[i].key; i++) + { + eina_hash_del(efreet_menu_filter_cbs, + filter_cbs[i].key, + NULL); + eina_hash_add(efreet_menu_filter_cbs, + filter_cbs[i].key, + filter_cbs[i].cb); + } + for (i = 0; move_cbs[i].key; i++) + { + eina_hash_del(efreet_menu_move_cbs, + move_cbs[i].key, + NULL); + eina_hash_add(efreet_menu_move_cbs, + move_cbs[i].key, + move_cbs[i].cb); + } + for (i = 0; layout_cbs[i].key; i++) + { + eina_hash_del(efreet_menu_layout_cbs, + layout_cbs[i].key, + NULL); + eina_hash_add(efreet_menu_layout_cbs, + layout_cbs[i].key, + layout_cbs[i].cb); + } + return 1; +} + +EAPI int +efreet_menu_kde_legacy_init(void) +{ + FILE *f; + char buf[PATH_MAX]; + char *p, *s; + + IF_FREE_LIST(efreet_menu_kde_legacy_dirs, eina_stringshare_del); + + f = popen("kde-config --path apps", "r"); + if (!f) return 0; + + /* XXX if the return from kde-config is a line longer than PATH_MAX, + * this won't be correct (increase buffer and get the rest...) */ + if (!fgets(buf, sizeof(buf), f)) + { + ERR("Error initializing KDE legacy information"); + return 0; + } + s = buf; + + p = strchr(s, ':'); + while (p) + { + *p = '\0'; + efreet_menu_kde_legacy_dirs = eina_list_append(efreet_menu_kde_legacy_dirs, + (void *)eina_stringshare_add(s)); + s = p + 1; + p = strchr(s, ':'); + } + + if (*s) + efreet_menu_kde_legacy_dirs = eina_list_append(efreet_menu_kde_legacy_dirs, + (void *)eina_stringshare_add(s)); + + pclose(f); + return 1; +} + +void +efreet_menu_shutdown(void) +{ + IF_RELEASE(efreet_menu_file); + + IF_FREE_HASH(efreet_menu_handle_cbs); + IF_FREE_HASH(efreet_menu_filter_cbs); + IF_FREE_HASH(efreet_menu_move_cbs); + IF_FREE_HASH(efreet_menu_layout_cbs); + + IF_FREE_LIST(efreet_menu_kde_legacy_dirs, eina_stringshare_del); + + IF_FREE_HASH(efreet_merged_menus); + IF_FREE_HASH(efreet_merged_dirs); + + IF_RELEASE(efreet_tag_menu); + + eina_log_domain_unregister(_efreet_menu_log_dom); + _efreet_menu_log_dom = -1; +} + +EAPI Efreet_Menu * +efreet_menu_new(const char *name) +{ + Efreet_Menu *menu; + + EINA_SAFETY_ON_NULL_RETURN_VAL(name, NULL); + + menu = efreet_menu_entry_new(); + menu->type = EFREET_MENU_ENTRY_MENU; + menu->name = eina_stringshare_add(name); + return menu; +} + +EAPI void +efreet_menu_file_set(const char *file) +{ + IF_RELEASE(efreet_menu_file); + efreet_menu_file = NULL; + if (file) efreet_menu_file = eina_stringshare_add(file); +} + +EAPI Efreet_Menu * +efreet_menu_get(void) +{ + char menu[PATH_MAX]; + const char *dir; + Eina_List *config_dirs, *l; + +#ifndef STRICT_SPEC + /* prefer user set menu */ + if (efreet_menu_file) + { + if (ecore_file_exists(efreet_menu_file)) + return efreet_menu_parse(efreet_menu_file); + } +#endif + + /* check the users config directory first */ + snprintf(menu, sizeof(menu), "%s/menus/%sapplications.menu", + efreet_config_home_get(), efreet_menu_prefix_get()); + if (ecore_file_exists(menu)) + return efreet_menu_parse(menu); + + /* fallback to the XDG_CONFIG_DIRS */ + config_dirs = efreet_config_dirs_get(); + EINA_LIST_FOREACH(config_dirs, l, dir) + { + snprintf(menu, sizeof(menu), "%s/menus/%sapplications.menu", + dir, efreet_menu_prefix_get()); + if (ecore_file_exists(menu)) + return efreet_menu_parse(menu); + } + + return NULL; +} + +EAPI Efreet_Menu * +efreet_menu_parse(const char *path) +{ + Efreet_Xml *xml; + Efreet_Menu_Internal *internal = NULL; + Efreet_Menu *entry = NULL; + + EINA_SAFETY_ON_NULL_RETURN_VAL(path, NULL); + + xml = efreet_xml_new(path); + if (!xml) return NULL; + + /* make sure we've got a to start with */ + if (xml->tag != efreet_tag_menu) + { + WRN("Efreet_menu: Menu file didn't start with tag."); + efreet_xml_del(xml); + return NULL; + } + + IF_FREE_HASH(efreet_merged_menus); + efreet_merged_menus = eina_hash_string_superfast_new(NULL); + + IF_FREE_HASH(efreet_merged_dirs); + efreet_merged_dirs = eina_hash_string_superfast_new(NULL); + + /* split apart the filename and the path */ + internal = efreet_menu_internal_new(); + if (!internal) return NULL; + + /* Set default values */ + internal->show_empty = 0; + internal->in_line = 0; + internal->inline_limit = 4; + internal->inline_header = 1; + internal->inline_alias = 0; + + efreet_menu_path_set(internal, path); + if (!efreet_menu_handle_menu(internal, xml)) + { + efreet_xml_del(xml); + efreet_menu_internal_free(internal); + return NULL; + } + efreet_xml_del(xml); + + efreet_menu_resolve_moves(internal); + + if (!efreet_menu_process_dirs(internal)) + { + efreet_menu_internal_free(internal); + return NULL; + } + + /* handle all .desktops */ + if (!efreet_menu_process(internal, 0)) + { + efreet_menu_internal_free(internal); + return NULL; + } + + /* handle menus with only unallocated .desktops */ + if (!efreet_menu_process(internal, 1)) + { + efreet_menu_internal_free(internal); + return NULL; + } + + /* layout menu */ + entry = efreet_menu_layout_menu(internal); + efreet_menu_internal_free(internal); + return entry; +} + +EAPI int +efreet_menu_save(Efreet_Menu *menu, const char *path) +{ + FILE *f; + int ret; + + EINA_SAFETY_ON_NULL_RETURN_VAL(menu, 0); + EINA_SAFETY_ON_NULL_RETURN_VAL(path, 0); + + f = fopen(path, "w"); + if (!f) return 0; + fprintf(f, "\n"); + fprintf(f, "\n"); + ret = efreet_menu_save_menu(menu, f, 0); + fclose(f); + return ret; +} + +static int +efreet_menu_save_menu(Efreet_Menu *menu, FILE *f, int indent) +{ + Eina_List *l; + + efreet_menu_save_indent(f, indent); + fprintf(f, "\n"); + if (menu->name) + { + efreet_menu_save_indent(f, indent + 1); + fprintf(f, "%s\n", menu->name); + } + + if (indent == 0) + { + /* Only save these for the root element */ + efreet_menu_save_indent(f, indent + 1); + fprintf(f, "\n"); + efreet_menu_save_indent(f, indent + 1); + fprintf(f, "\n"); + } + + if (menu->desktop) + { + efreet_menu_save_indent(f, indent + 1); + fprintf(f, "%s\n", menu->desktop->orig_path); + } + + if (menu->entries) + { + Efreet_Menu *entry; + int has_desktop = 0, has_menu = 0; + + efreet_menu_save_indent(f, indent + 1); + fprintf(f, "\n"); + EINA_LIST_FOREACH(menu->entries, l, entry) + { + if (entry->type == EFREET_MENU_ENTRY_MENU) + { + efreet_menu_save_indent(f, indent + 2); + fprintf(f, "%s\n", entry->id); + has_menu = 1; + } + else if (entry->type == EFREET_MENU_ENTRY_DESKTOP) + { + efreet_menu_save_indent(f, indent + 2); + fprintf(f, "%s\n", entry->id); + has_desktop = 1; + } + else if (entry->type == EFREET_MENU_ENTRY_SEPARATOR) + { + efreet_menu_save_indent(f, indent + 2); + fprintf(f, "\n"); + } + } + efreet_menu_save_indent(f, indent + 1); + fprintf(f, "\n"); + + if (has_desktop) + { + efreet_menu_save_indent(f, indent + 1); + fprintf(f, "\n"); + EINA_LIST_FOREACH(menu->entries, l, entry) + { + if (entry->type == EFREET_MENU_ENTRY_DESKTOP) + { + efreet_menu_save_indent(f, indent + 2); + fprintf(f, "%s\n", entry->id); + } + } + efreet_menu_save_indent(f, indent + 1); + fprintf(f, "\n"); + } + + if (has_menu) + { + EINA_LIST_FOREACH(menu->entries, l, entry) + { + if (entry->type == EFREET_MENU_ENTRY_MENU) + efreet_menu_save_menu(entry, f, indent + 1); + } + } + } + efreet_menu_save_indent(f, indent); + fprintf(f, "\n"); + return 1; +} + +static int +efreet_menu_save_indent(FILE *f, int indent) +{ + int i; + + for (i = 0; i < indent; i++) + fprintf(f, " "); + return 1; +} + +EAPI int +efreet_menu_desktop_insert(Efreet_Menu *menu, Efreet_Desktop *desktop, int pos) +{ + Efreet_Menu *entry; + const char *id; + + EINA_SAFETY_ON_NULL_RETURN_VAL(menu, 0); + EINA_SAFETY_ON_NULL_RETURN_VAL(desktop, 0); + + id = efreet_util_path_to_file_id(desktop->orig_path); + if (!id) return 0; + + entry = efreet_menu_entry_new(); + entry->type = EFREET_MENU_ENTRY_DESKTOP; + entry->id = eina_stringshare_add(id); + entry->name = eina_stringshare_add(desktop->name); + if (desktop->icon) entry->icon = eina_stringshare_add(desktop->icon); + efreet_desktop_ref(desktop); + entry->desktop = desktop; + + if (pos < 0 || (unsigned int)pos >= eina_list_count(menu->entries)) + menu->entries = eina_list_append(menu->entries, entry); + else + { + menu->entries = eina_list_append_relative(menu->entries, entry, + eina_list_nth(menu->entries, pos)); + } + return 1; +} + +EAPI int +efreet_menu_desktop_remove(Efreet_Menu *menu, Efreet_Desktop *desktop) +{ + Efreet_Menu *entry; + + EINA_SAFETY_ON_NULL_RETURN_VAL(menu, 0); + EINA_SAFETY_ON_NULL_RETURN_VAL(desktop, 0); + + entry = eina_list_search_unsorted(menu->entries, + EINA_COMPARE_CB(efreet_menu_cb_entry_compare_desktop), + desktop); + if (entry) + { + menu->entries = eina_list_remove(menu->entries, entry); + efreet_menu_free(entry); + return 1; + } + return 0; +} + +EAPI void +efreet_menu_dump(Efreet_Menu *menu, const char *indent) +{ + Eina_List *l; + + EINA_SAFETY_ON_NULL_RETURN(menu); + EINA_SAFETY_ON_NULL_RETURN(indent); + + INF("%s%s: ", indent, menu->name); + INF("%s", (menu->icon ? menu->icon : "No icon")); + + /* XXX dump the rest of the menu info */ + + if (menu->entries) + { + Efreet_Menu *entry; + char *new_indent; + size_t len; + + len = strlen(indent) + 3; + new_indent = alloca(len); + snprintf(new_indent, len, "%s ", indent); + + EINA_LIST_FOREACH(menu->entries, l, entry) + { + if (entry->type == EFREET_MENU_ENTRY_SEPARATOR) + INF("%s|---", new_indent); + else if (entry->type == EFREET_MENU_ENTRY_DESKTOP) + INF("%s|-%s", new_indent, entry->name); + else if (entry->type == EFREET_MENU_ENTRY_MENU) + efreet_menu_dump(entry, new_indent); + else if (entry->type == EFREET_MENU_ENTRY_HEADER) + INF("%s|---%s", new_indent, entry->name); + } + } +} + +/** + * @internal + * @param user_dir The user directory to work with + * @param system_dirs The system directories to work with + * @param suffix The path suffix to add + * @return Returns the list of directories + * @brief Creates the list of directories based on the user + * dir, system dirs and given suffix. + * + * Needs EAPI because of helper binaries + */ +EAPI Eina_List * +efreet_default_dirs_get(const char *user_dir, Eina_List *system_dirs, + const char *suffix) +{ + const char *xdg_dir; + char dir[PATH_MAX]; + Eina_List *list = NULL; + Eina_List *l; + + EINA_SAFETY_ON_NULL_RETURN_VAL(user_dir, NULL); + EINA_SAFETY_ON_NULL_RETURN_VAL(suffix, NULL); + + snprintf(dir, sizeof(dir), "%s/%s", user_dir, suffix); + list = eina_list_append(list, eina_stringshare_add(dir)); + + EINA_LIST_FOREACH(system_dirs, l, xdg_dir) + { + snprintf(dir, sizeof(dir), "%s/%s", xdg_dir, suffix); + list = eina_list_append(list, eina_stringshare_add(dir)); + } + + return list; +} + +/** + * @internal + * @return Returns a new Efreet_Menu_Internal struct + * @brief Allocates and initializes a new Efreet_Menu_Internal structure + */ +static Efreet_Menu_Internal * +efreet_menu_internal_new(void) +{ + Efreet_Menu_Internal *internal; + + internal = NEW(Efreet_Menu_Internal, 1); + if (!internal) return NULL; + internal->show_empty = -1; + internal->in_line = -1; + internal->inline_limit = -1; + internal->inline_header = -1; + internal->inline_alias = -1; + + return internal; +} + +/** + * @param menu The menu to free + * @return Returns no value + * @brief Frees up the given menu structure + */ +void +efreet_menu_internal_free(Efreet_Menu_Internal *internal) +{ + if (!internal) return; + + IF_RELEASE(internal->file.path); + IF_RELEASE(internal->file.name); + + IF_RELEASE(internal->name.internal); + internal->name.name = NULL; + + internal->applications = eina_list_free(internal->applications); + + IF_FREE_LIST(internal->directories, eina_stringshare_del); + IF_FREE_LIST(internal->app_dirs, efreet_menu_app_dir_free); + IF_FREE_LIST(internal->app_pool, efreet_menu_desktop_free); + IF_FREE_LIST(internal->directory_dirs, eina_stringshare_del); + IF_FREE_HASH(internal->directory_cache); + + IF_FREE_LIST(internal->moves, efreet_menu_move_free); + IF_FREE_LIST(internal->filters, efreet_menu_filter_free); + + IF_FREE_LIST(internal->sub_menus, efreet_menu_internal_free); + + IF_FREE_LIST(internal->layout, efreet_menu_layout_free); + IF_FREE_LIST(internal->default_layout, efreet_menu_layout_free); + + FREE(internal); +} + +/** + * @internal + * @return Returns the XDG_MENU_PREFIX env variable or "" if none set + * @brief Retrieves the XDG_MENU_PREFIX or "" if not set. + */ +static const char * +efreet_menu_prefix_get(void) +{ + if (efreet_menu_prefix) return efreet_menu_prefix; + + efreet_menu_prefix = getenv("XDG_MENU_PREFIX"); + if (!efreet_menu_prefix) efreet_menu_prefix = ""; + + return efreet_menu_prefix; +} + +/** + * @internal + * @param menu The menu to populate + * @param xml The xml dom tree to populate from + * @return Returns 1 if this XML tree is valid, 0 otherwise + * @brief Populates the given menu from the given xml structure + * + * We walk the Menu children backwards. The reason for this is so that we + * can deal with all the things that make us select the 'last' element + * (MergeFile, Directory, etc). We'll see the last one first and can deal + * with it right away. + */ +static int +efreet_menu_handle_menu(Efreet_Menu_Internal *internal, Efreet_Xml *xml) +{ + Efreet_Xml *child; + Eina_List *l; + int (*cb)(Efreet_Menu_Internal *parent, Efreet_Xml *xml); + + EINA_LIST_REVERSE_FOREACH(xml->children, l, child) + { + cb = eina_hash_find(efreet_menu_handle_cbs, child->tag); + if (cb) + { + if (!cb(internal, child)) + return 0; + } + else + { + WRN("Unknown XML tag: %s", child->tag); + return 0; + } + } + return 1; +} + +/** + * @internal + * @param parent The parent Menu + * @param xml The xml that defines the menu + * @return Returns 1 on success or 0 on failure + * @brief Handles the sub-menu nodes of the XML file + */ +static int +efreet_menu_handle_sub_menu(Efreet_Menu_Internal *parent, Efreet_Xml *xml) +{ + Efreet_Menu_Internal *internal, *match; + + efreet_menu_create_sub_menu_list(parent); + + internal = efreet_menu_internal_new(); + if (!internal) return 0; + internal->file.path = eina_stringshare_add(parent->file.path); + if (!efreet_menu_handle_menu(internal, xml)) + { + efreet_menu_internal_free(internal); + return 0; + } + + /* if this menu already exists we just take this one and stick it on the + * start of the existing one */ + if ((match = eina_list_search_unsorted(parent->sub_menus, + EINA_COMPARE_CB(efreet_menu_cb_menu_compare), + internal))) + { + + efreet_menu_concatenate(match, internal); + efreet_menu_internal_free(internal); + } + else + parent->sub_menus = eina_list_prepend(parent->sub_menus, internal); + + return 1; +} + +/** + * @internal + * @param parent The parent menu + * @param xml The xml tree + * @return Returns 1 on success or 0 on failure + * @brief Handles the AppDir tag + */ +static int +efreet_menu_handle_app_dir(Efreet_Menu_Internal *parent, Efreet_Xml *xml) +{ + const char *path; + Efreet_Menu_App_Dir *app_dir; + + if (!parent || !xml) return 0; + + efreet_menu_create_app_dirs_list(parent); + path = efreet_menu_path_get(parent, xml->text); + if (!path) return 0; + + /* we've already got this guy in our list we can skip it */ + if (eina_list_search_unsorted(parent->app_dirs, + EINA_COMPARE_CB(efreet_menu_cb_app_dirs_compare), + path)) + { + eina_stringshare_del(path); + return 1; + } + + app_dir = efreet_menu_app_dir_new(); + app_dir->path = path; + + parent->app_dirs = eina_list_prepend(parent->app_dirs, app_dir); + + return 1; +} + +/** + * @internal + * @param parent The parent menu + * @param xml UNUSED + * @return Returns 1 on success or 0 on failure + * @brief Handles the DefaultAppDirs + */ +static int +efreet_menu_handle_default_app_dirs(Efreet_Menu_Internal *parent, Efreet_Xml *xml __UNUSED__) +{ + Eina_List *prepend = NULL; + Eina_List *dirs; + char *dir; + + if (!parent) return 0; + + efreet_menu_create_app_dirs_list(parent); + dirs = efreet_default_dirs_get(efreet_data_home_get(), efreet_data_dirs_get(), + "applications"); + EINA_LIST_FREE(dirs, dir) + { + if (!eina_list_search_unsorted(parent->app_dirs, + EINA_COMPARE_CB(efreet_menu_cb_app_dirs_compare), + dir)) + { + Efreet_Menu_App_Dir *app_dir; + + app_dir = efreet_menu_app_dir_new(); + app_dir->path = eina_stringshare_ref(dir); + + prepend = eina_list_append(prepend, app_dir); + } + + eina_stringshare_del(dir); + } + parent->app_dirs = eina_list_merge(prepend, parent->app_dirs); + + return 1; +} + +/** + * @internal + * @param parent The parent menu + * @param xml The xml tree + * @return Returns 1 on success or 0 on failure + * @brief Handles the DirectoryDir tag + */ +static int +efreet_menu_handle_directory_dir(Efreet_Menu_Internal *parent, Efreet_Xml *xml) +{ + const char *path; + + if (!parent || !xml) return 0; + + efreet_menu_create_directory_dirs_list(parent); + path = efreet_menu_path_get(parent, xml->text); + if (!path) return 0; + + /* we've already got this guy in our list we can skip it */ + if (eina_list_search_unsorted(parent->directory_dirs, EINA_COMPARE_CB(strcmp), path)) + { + eina_stringshare_del(path); + return 1; + } + + parent->directory_dirs = eina_list_prepend(parent->directory_dirs, path); + + return 1; +} + +/** + * @internal + * @param parent The parent menu + * @param xml UNUSED + * @return Returns 1 on success or 0 on failure + * @brief Handles the DefaultDirectoryDirs tag + */ +static int +efreet_menu_handle_default_directory_dirs(Efreet_Menu_Internal *parent, Efreet_Xml *xml __UNUSED__) +{ + Eina_List *dirs; + char *dir; + + if (!parent) return 0; + + efreet_menu_create_directory_dirs_list(parent); + dirs = efreet_default_dirs_get(efreet_data_home_get(), efreet_data_dirs_get(), + "desktop-directories"); + EINA_LIST_FREE(dirs, dir) + { + if (!eina_list_search_unsorted(parent->directory_dirs, EINA_COMPARE_CB(strcmp), dir)) + parent->directory_dirs = eina_list_prepend(parent->directory_dirs, eina_stringshare_ref(dir)); + eina_stringshare_del(dir); + } + + return 1; +} + +/** + * @internal + * @param parent The parent Menu + * @param xml The xml to work with + * @return Returns 1 on success or 0 on failure + * @brief Sets the menu name from the given XML fragment. + */ +static int +efreet_menu_handle_name(Efreet_Menu_Internal *parent, Efreet_Xml *xml) +{ + /* not allowed to have two Name settings in a menu */ + if (parent->name.internal) + { + INF("efreet_menu_handle_name() setting second name into menu"); + return 0; + } + /* ignore the name if it is empty */ + if (!xml->text) return 1; + + /* ignore the name if it contains a / */ + if (strchr(xml->text, '/')) return 1; + + parent->name.internal = eina_stringshare_add(xml->text); + + return 1; +} + +/** + * @internal + * @param parent The parent menu + * @param xml The xml tree + * @return Returns 1 on success or 0 on failure + * @brief Handles the Directory tag + * + * This just adds the given directory path to a list which we'll walk once + * we've traversed the entire menu into memory. + */ +static int +efreet_menu_handle_directory(Efreet_Menu_Internal *parent, Efreet_Xml *xml) +{ + if (!parent || !xml) return 0; + + efreet_menu_create_directories_list(parent); + parent->directories = eina_list_prepend(parent->directories, eina_stringshare_add(xml->text)); + + return 1; +} + +/** + * @internal + * @param parent The parent menu + * @param xml The xml tree + * @return Returns 1 on success or 0 on failure + * @brief Handles the OnlyUnallocated tag + */ +static int +efreet_menu_handle_only_unallocated(Efreet_Menu_Internal *parent, Efreet_Xml *xml) +{ + if (!parent || !xml) return 0; + + /* a later instance has been seen so we can ignore this one */ + if (parent->seen_allocated) return 1; + + parent->seen_allocated = 1; + parent->only_unallocated = 1; + + return 1; +} + +/** + * @internal + * @param parent The parent menu + * @param xml The xml tree + * @return Returns 1 on success or 0 on failure + * @brief Handles the NotOnlyUnallocated tag + */ +static int +efreet_menu_handle_not_only_unallocated(Efreet_Menu_Internal *parent, Efreet_Xml *xml) +{ + if (!parent || !xml) return 0; + + /* a later instance has been seen so we can ignore this one */ + if (parent->seen_allocated) return 1; + + parent->seen_allocated = 1; + parent->only_unallocated = 0; + + return 1; +} + +/** + * @internal + * @param parent The parent menu + * @param xml The xml tree + * @return Returns 1 on success or 0 on failure + * @brief Handles the Deleted tag + */ +static int +efreet_menu_handle_deleted(Efreet_Menu_Internal *parent, Efreet_Xml *xml) +{ + if (!parent || !xml) return 0; + + /* a later instance has been seen so we can ignore this one */ + if (parent->seen_deleted) return 1; + + parent->seen_deleted = 1; + parent->deleted = 1; + + return 1; +} + +/** + * @internal + * @param parent The parent menu + * @param xml The xml tree + * @return Returns 1 on success or 0 on failure + * @brief Handles the NotDeleted tag + */ +static int +efreet_menu_handle_not_deleted(Efreet_Menu_Internal *parent, Efreet_Xml *xml) +{ + if (!parent || !xml) return 0; + + /* a later instance has been seen so we can ignore this one */ + if (parent->seen_deleted) return 1; + + parent->seen_deleted = 1; + parent->deleted = 0; + + return 1; +} + +/** + * @internal + * @param parent The parent menu + * @param xml The XML tree to work with + * @return Returns 1 on success or 0 on failure + * @brief Handles parsing the Include tag and all subtags + */ +static int +efreet_menu_handle_include(Efreet_Menu_Internal *parent, Efreet_Xml *xml) +{ + return efreet_menu_handle_filter(parent, xml, + EFREET_MENU_FILTER_INCLUDE); +} + +/** + * @internal + * @param parent The parent menu + * @param xml The xml tree + * @return Returns 1 on success or 0 on failure + * @brief Handles the Exclude tag and all subtags + */ +static int +efreet_menu_handle_exclude(Efreet_Menu_Internal *parent, Efreet_Xml *xml) +{ + return efreet_menu_handle_filter(parent, xml, + EFREET_MENU_FILTER_EXCLUDE); +} + +/** + * @internal + * @param op The filter operation + * @param xml The xml tree + * @return Returns 1 on success or 0 on failure + * @brief Handles the Filename tag + */ +static int +efreet_menu_handle_filename(Efreet_Menu_Filter_Op *op, Efreet_Xml *xml) +{ + if (!op || !xml) return 0; + + op->filenames = eina_list_append(op->filenames, eina_stringshare_add(xml->text)); + + return 1; +} + +/** + * @internal + * @param op The filter operation + * @param xml The xml tree + * @return Returns 1 on success or 0 on failure + * @brief Handles the Category tag + */ +static int +efreet_menu_handle_category(Efreet_Menu_Filter_Op *op, Efreet_Xml *xml) +{ + if (!op || !xml) return 0; + + + op->categories = eina_list_append(op->categories, eina_stringshare_add(xml->text)); + + return 1; +} + +/** + * @internal + * @param op The filter operation + * @param xml The xml tree + * @return Returns 1 on success or 0 on failure + * @brief Handles the All tag and all subtags + */ +static int +efreet_menu_handle_all(Efreet_Menu_Filter_Op *op, Efreet_Xml *xml) +{ + if (!op || !xml) return 0; + + op->all = 1; + + return 1; +} + +/** + * @internal + * @param op The filter operation + * @param xml The xml tree + * @return Returns 1 on success or 0 on failure + * @brief Handles the And tag and all subtags + */ +static int +efreet_menu_handle_and(Efreet_Menu_Filter_Op *op, Efreet_Xml *xml) +{ + if (!op || !xml) return 0; + + return efreet_menu_handle_filter_child_op(op, xml, + EFREET_MENU_FILTER_OP_AND); +} + +/** + * @internal + * @param op The filter operation + * @param xml The xml tree + * @return Returns 1 on success or 0 on failure + * @brief Handles the Or tag and all subtags + */ +static int +efreet_menu_handle_or(Efreet_Menu_Filter_Op *op, Efreet_Xml *xml) +{ + if (!op || !xml) return 0; + + return efreet_menu_handle_filter_child_op(op, xml, + EFREET_MENU_FILTER_OP_OR); +} + +/** + * @internal + * @param op The filter operation + * @param xml The xml tree + * @return Returns 1 on success or 0 on failure + * @brief Handles the Not tag and all subtags + */ +static int +efreet_menu_handle_not(Efreet_Menu_Filter_Op *op, Efreet_Xml *xml) +{ + if (!op || !xml) return 0; + + return efreet_menu_handle_filter_child_op(op, xml, + EFREET_MENU_FILTER_OP_NOT); +} + +/** + * @internal + * @param parent The parent menu + * @param xml The xml tree + * @return Returns 1 on success or 0 on failure + * @brief Handles the MergeFile tag + */ +static int +efreet_menu_handle_merge_file(Efreet_Menu_Internal *parent, Efreet_Xml *xml) +{ + Eina_List *l; + const char *path = NULL; + const char *attr = NULL; + int is_path = 1; + int ret = 1; + + if (!parent || !xml) return 0; + + /* check to see if this is a path or parent type */ + attr = efreet_xml_attribute_get(xml, "type"); + if (attr && !strcmp(attr, "parent")) + is_path = 0; + + /* we're given a path */ + if (is_path) + path = efreet_menu_path_get(parent, xml->text); + + /* need to find the next menu with the same name as ours in the config + * dir after ours (if we're in a config dir) */ + else + { + Eina_List *search_dirs; + const char *dir, *p; + + if (!parent->file.path) + { + INF("efreet_menu_handle_merge_file() missing menu path ..."); + return 0; + } + + search_dirs = efreet_config_dirs_get(); + + /* we need to find the next menu with the same name in the directory + * after the on the the menu was found in. to do that we first check + * if it's in the config_home_directory() if so we need to search + * all of the dirs. If it isn't in the config home directory then we + * scan the search dirs and look for it. The search_dirs list will + * be left at the next pointer so we can start looking for the menu + * from that point */ + + dir = efreet_config_home_get(); + if (strncmp(dir, parent->file.path, eina_stringshare_strlen(dir))) + { + EINA_LIST_FOREACH(search_dirs, l, dir) + { + if (!strncmp(dir, parent->file.path, eina_stringshare_strlen(dir))) + break; + } + } + + if (!dir) + { + INF("efreet_menu_handle_merge_file() failed to find " + "menu parent directory"); + return 0; + } + + /* the parent file path may have more path then just the base + * directory so we need to append that as well */ + p = parent->file.path + eina_stringshare_strlen(dir); + + /* whatever dirs are left in the search dir we need to look for the + * menu with the same relative filename */ + EINA_LIST_FOREACH(search_dirs, l, dir) + { + char file[PATH_MAX]; + + snprintf(file, sizeof(file), "%s/%s/%s", dir, (p ? p : ""), + parent->file.name); + if (ecore_file_exists(file)) + { + path = eina_stringshare_add(file); + break; + } + } + } + + /* nothing to do if no file found */ + if (!path) return 1; + + if (!efreet_menu_merge(parent, xml, path)) + ret = 0; + + eina_stringshare_del(path); + + return ret; +} + +/** + * @internal + * @param parent The parent menu to merge into + * @param xml The XML to be merged + * @param path The path to the .menu file to merge + */ +static int +efreet_menu_merge(Efreet_Menu_Internal *parent, Efreet_Xml *xml, const char *path) +{ + Efreet_Xml *merge_xml; + Efreet_Menu_Internal *internal; + char rp[PATH_MAX]; + + if (!parent || !xml || !path) return 0; + + /* do nothing if the file doesn't exist */ + if (!ecore_file_exists(path)) return 1; + + if (!realpath(path, rp)) + { + INF("efreet_menu_merge() unable to get real path for %s", path); + return 0; + } + + /* don't merge the same path twice */ + if (eina_hash_find(efreet_merged_menus, rp)) + { + return 1; + } + + eina_hash_add(efreet_merged_menus, rp, (void *)1); + + merge_xml = efreet_xml_new(rp); + + if (!merge_xml) + { + INF("efreet_menu_merge() failed to read in the " + "merge file (%s)", rp); + return 0; + } + + internal = efreet_menu_internal_new(); + if (!internal) return 0; + efreet_menu_path_set(internal, path); + efreet_menu_handle_menu(internal, merge_xml); + efreet_menu_concatenate(parent, internal); + efreet_menu_internal_free(internal); + + efreet_xml_del(merge_xml); + + return 1; +} + +/** + * @internal + * @param parent The parent menu + * @param xml The xml tree + * @return Returns 1 on success or 0 on failure + * @brief Handles the MergeDir tag + */ +static int +efreet_menu_handle_merge_dir(Efreet_Menu_Internal *parent, Efreet_Xml *xml) +{ + const char *path; + int ret; + + if (!parent || !xml || !xml->text) return 0; + + path = efreet_menu_path_get(parent, xml->text); + if (!path) return 1; + if (!ecore_file_exists(path)) + { + eina_stringshare_del(path); + return 1; + } + + ret = efreet_menu_merge_dir(parent, xml, path); + eina_stringshare_del(path); + + return ret; +} + +/** + * @internal + * @param parent the parent menu of the merge + * @param xml The xml tree + * @param path The path to the merge directory + * @return Returns 1 on success or 0 on failure + * @brief Find all of the .menu files in the given directory and merge them + * into the @a parent menu. + */ +static int +efreet_menu_merge_dir(Efreet_Menu_Internal *parent, Efreet_Xml *xml, const char *path) +{ + Eina_Iterator *it; + Eina_File_Direct_Info *info; + + if (!parent || !xml || !path) return 0; + + /* check to see if we've merged this directory already */ + if (eina_hash_find(efreet_merged_dirs, path)) return 1; + eina_hash_add(efreet_merged_dirs, path, (void *)1); + + it = eina_file_direct_ls(path); + if (!it) return 1; + + EINA_ITERATOR_FOREACH(it, info) + { + char *p; + + p = strrchr(info->path + info->name_start, '.'); + if (!p) continue; + if (strcmp(p, ".menu")) continue; + + if (!efreet_menu_merge(parent, xml, info->path)) + { + eina_iterator_free(it); + return 0; + } + } + eina_iterator_free(it); + + return 1; +} + +/** + * @internal + * @param parent The parent menu + * @param xml The xml tree + * @return Returns 1 on success or 0 on failure + * @brief Handles the DefaultMergeDirs tag + */ +static int +efreet_menu_handle_default_merge_dirs(Efreet_Menu_Internal *parent, Efreet_Xml *xml) +{ + Eina_List *dirs; + char path[PATH_MAX], *p, *pp; +#ifndef STRICT_SPEC + char parent_path[PATH_MAX]; +#endif + const char *prefix; + + if (!parent || !xml) return 0; + + prefix = efreet_menu_prefix_get(); + if (!strcmp(prefix, "gnome-") && + (!strcmp(parent->file.name, "gnome-applications.menu"))) + { + p = alloca(sizeof("applications")); + memcpy(p, "applications", sizeof("applications")); + } + else if ((!strcmp(prefix, "kde-") && + (!strcmp(parent->file.name, "kde-applications.menu")))) + { + p = alloca(sizeof("applications")); + memcpy(p, "applications", sizeof("applications")); + } + else + { + char *s; + size_t len; + + len = strlen(parent->file.name) + 1; + p = alloca(len); + memcpy(p, parent->file.name, len); + s = strrchr(p, '.'); + if (s) *s = '\0'; + } + snprintf(path, sizeof(path), "menus/%s-merged", p); + + dirs = efreet_default_dirs_get(efreet_config_home_get(), + efreet_config_dirs_get(), path); + + EINA_LIST_FREE(dirs, pp) + { + efreet_menu_merge_dir(parent, xml, pp); + eina_stringshare_del(pp); + } +#ifndef STRICT_SPEC + /* Also check the path of the parent file */ + snprintf(parent_path, sizeof(parent_path), "%s/%s", parent->file.path, path); + efreet_menu_merge_dir(parent, xml, parent_path); +#endif + + return 1; +} + +/** + * @internal + * @param parent The parent menu + * @param xml The xml tree + * @return Returns 1 on success or 0 on failure + * @brief Handles the LegacyDir tag + */ +static int +efreet_menu_handle_legacy_dir(Efreet_Menu_Internal *parent, Efreet_Xml *xml) +{ + Efreet_Menu_Internal *legacy; + + if (!parent || !xml) return 0; + + legacy = efreet_menu_handle_legacy_dir_helper(NULL, parent, xml->text, + efreet_xml_attribute_get(xml, "prefix")); + if (legacy) + { + efreet_menu_concatenate(parent, legacy); + efreet_menu_internal_free(legacy); + } + + return 1; + +} + +/** + * @internal + * @param parent The parent menu + * @param legacy_dir The legacy directory path + * @param prefix The legacy directory prefix if one set + * @return Returns the Efreet_Menu_Internal representing the legacy hierarchy + * @brief Handles the process of merging @a legacy_dir into @a parent menu + */ +static Efreet_Menu_Internal * +efreet_menu_handle_legacy_dir_helper(Efreet_Menu_Internal *root, + Efreet_Menu_Internal *parent, + const char *legacy_dir, + const char *prefix) +{ + const char *path; + Efreet_Menu_Internal *legacy_internal; + Efreet_Menu_Filter *filter; + Efreet_Menu_App_Dir *app_dir; + int count = 0; + Eina_Iterator *it; + + if (!parent || !legacy_dir) return 0; + + path = efreet_menu_path_get(parent, legacy_dir); + + /* nothing to do if the legacy path doesn't exist */ + if (!path || !ecore_file_exists(path)) + { + eina_stringshare_del(path); + return NULL; + } + + legacy_internal = efreet_menu_internal_new(); + if (!legacy_internal) + return NULL; + legacy_internal->name.internal = eina_stringshare_add(ecore_file_file_get(path)); + + /* add the legacy dir as an app dir */ + app_dir = efreet_menu_app_dir_new(); + app_dir->path = eina_stringshare_add(path); + app_dir->legacy = 1; + if (prefix && !strchr(prefix, '/')) app_dir->prefix = eina_stringshare_add(prefix); + + efreet_menu_create_app_dirs_list(legacy_internal); + legacy_internal->app_dirs = eina_list_append(legacy_internal->app_dirs, app_dir); +#ifndef STRICT_SPEC + if (root) + { + /* XXX This seems wrong, but it makes efreet pass the fdo tests */ + app_dir = efreet_menu_app_dir_new(); + app_dir->path = eina_stringshare_add(path); + app_dir->legacy = 1; + if (prefix && !strchr(prefix, '/')) app_dir->prefix = eina_stringshare_add(prefix); + root->app_dirs = eina_list_append(root->app_dirs, app_dir); + } +#endif + + /* add the legacy dir as a directory dir */ + efreet_menu_create_directory_dirs_list(legacy_internal); + legacy_internal->directory_dirs = eina_list_append(legacy_internal->directory_dirs, eina_stringshare_add(path)); + + /* setup a filter for all the conforming .desktop files in the legacy + * dir */ + filter = efreet_menu_filter_new(); + if (!filter) + { + efreet_menu_internal_free(legacy_internal); + return NULL; + } + filter->type = EFREET_MENU_FILTER_INCLUDE; + + filter->op->type = EFREET_MENU_FILTER_OP_OR; + + efreet_menu_create_filter_list(legacy_internal); + legacy_internal->filters = eina_list_append(legacy_internal->filters, filter); + + it = eina_file_direct_ls(path); + if (it) + { + Eina_File_Direct_Info *info; + + EINA_ITERATOR_FOREACH(it, info) + { + Efreet_Desktop *desktop = NULL; + char buf[PATH_MAX]; + char *exten; + const char *fname; + + fname = info->path + info->name_start; + /* recurse into sub directories */ + if (ecore_file_is_dir(info->path)) + { + Efreet_Menu_Internal *ret; + + ret = efreet_menu_handle_legacy_dir_helper(root ? root : legacy_internal, + legacy_internal, info->path, prefix); + if (!ret) + { + efreet_menu_internal_free(legacy_internal); + eina_stringshare_del(path); + eina_iterator_free(it); + return NULL; + } + + efreet_menu_create_sub_menu_list(legacy_internal); + legacy_internal->sub_menus = eina_list_prepend(legacy_internal->sub_menus, ret); + + continue; + } + + if (!strcmp(fname, ".directory")) + { + legacy_internal->directory = efreet_desktop_get(info->path); + if (legacy_internal->directory + && legacy_internal->directory->type != EFREET_DESKTOP_TYPE_DIRECTORY) + { + efreet_desktop_free(legacy_internal->directory); + legacy_internal->directory = NULL; + } + continue; + } + + exten = strrchr(fname, '.'); + + if (exten && !strcmp(exten, ".desktop")) + desktop = efreet_desktop_get(info->path); + + if (!desktop) continue; + + /* if the .desktop has categories it isn't legacy */ + if (efreet_desktop_category_count_get(desktop) != 0) + { + efreet_desktop_free(desktop); + continue; + } + + /* XXX: This will disappear when the .desktop is free'd */ + efreet_desktop_category_add(desktop, "Legacy"); + + if (prefix) + { + snprintf(buf, sizeof(buf), "%s%s", prefix, fname); + filter->op->filenames = eina_list_append(filter->op->filenames, eina_stringshare_add(buf)); + } + else + filter->op->filenames = eina_list_append(filter->op->filenames, eina_stringshare_add(fname)); + + count++; + efreet_desktop_free(desktop); + } + eina_iterator_free(it); + } + + eina_stringshare_del(path); + return legacy_internal; +} + +/** + * @internal + * @param parent The parent menu + * @param xml UNUSED + * @return Returns 1 on success or 0 on failure + * @brief Handles the KDELegacyDirs tag + */ +static int +efreet_menu_handle_kde_legacy_dirs(Efreet_Menu_Internal *parent, Efreet_Xml *xml __UNUSED__) +{ + Eina_List *l; + const char *dir; + + if (!parent) return 0; + + if (!efreet_menu_kde_legacy_dirs) return 1; + + /* XXX if one _helper() call succeeds, we return success. should this be flipped? + * (return fail if on of them failed) */ + EINA_LIST_FOREACH(efreet_menu_kde_legacy_dirs, l, dir) + { + Efreet_Menu_Internal *kde; + + kde = efreet_menu_handle_legacy_dir_helper(NULL, parent, dir, "kde"); + if (kde) + { + efreet_menu_concatenate(parent, kde); + efreet_menu_internal_free(kde); + return 1; + } + } + + return 0; +} + +/** + * @internal + * @param parent The parent menu + * @param xml The xml tree + * @return Returns 1 on success or 0 on failure + * @brief Handles the Move tag and all subtags + */ +static int +efreet_menu_handle_move(Efreet_Menu_Internal *parent, Efreet_Xml *xml) +{ + Efreet_Xml *child; + Eina_List *l; + + if (!parent || !xml) return 0; + + efreet_menu_create_move_list(parent); + + EINA_LIST_FOREACH(xml->children, l, child) + { + int (*cb)(Efreet_Menu_Internal *parent, Efreet_Xml *xml); + + cb = eina_hash_find(efreet_menu_move_cbs, child->tag); + if (cb) + { + if (!cb(parent, child)) + return 0; + } + else + { + INF("efreet_menu_handle_move() unknown tag found " + "in Move (%s)", child->tag); + return 0; + } + } + + parent->current_move = NULL; + + return 1; +} + +/** + * @internal + * @param parent The parent menu + * @param xml The xml tree + * @return Returns 1 on success or 0 on failure + * @brief Handles the Old tag + */ +static int +efreet_menu_handle_old(Efreet_Menu_Internal *parent, Efreet_Xml *xml) +{ + Efreet_Menu_Move *move; + + if (!parent || !xml || !xml->text) return 0; + + if (parent->current_move) + { + INF("efreet_menu_handle_old() saw second " + "before seeing "); + return 0; + } + + /* If we already moved this menu, remove the old move */ + /* XXX This seems wrong, but it makes efreet pass the fdo tests */ +#ifndef STRICT_SPEC + move = eina_list_search_unsorted(parent->moves, + EINA_COMPARE_CB(efreet_menu_cb_move_compare), + xml->text); + if (move) + { + efreet_menu_move_free(move); + parent->moves = eina_list_remove(parent->moves, move); + } +#endif + + move = efreet_menu_move_new(); + move->old_name = eina_stringshare_add(xml->text); + + parent->current_move = move; + parent->moves = eina_list_append(parent->moves, move); + + return 1; +} + +/** + * @internal + * @param parent The parent menu + * @param xml The xml tree + * @return Returns 1 on success or 0 on failure + * @brief Handles the New tag + */ +static int +efreet_menu_handle_new(Efreet_Menu_Internal *parent, Efreet_Xml *xml) +{ + if (!parent || !xml || !xml->text) return 0; + + if (!parent->current_move) + { + INF("efreet_menu_handle_new() saw New before seeing Old"); + return 0; + } + + parent->current_move->new_name = eina_stringshare_add(xml->text); + parent->current_move = NULL; + + return 1; +} + +/** + * @internal + * @param parent The parent menu + * @param xml The xml tree + * @return Returns 1 on success or 0 on failure + * @brief Handles the Layout tag and all subtags + */ +static int +efreet_menu_handle_layout(Efreet_Menu_Internal *parent, Efreet_Xml *xml) +{ + Efreet_Xml *child; + Eina_List *l; + + if (!parent || !xml) return 0; + + /* We use the last existing layout */ + if (parent->layout) return 1; + + efreet_menu_create_layout_list(parent); + + EINA_LIST_FOREACH(xml->children, l, child) + { + int (*cb)(Efreet_Menu_Internal *parent, Efreet_Xml *xml, int def); + + cb = eina_hash_find(efreet_menu_layout_cbs, child->tag); + if (cb) + { + if (!cb(parent, child, 0)) + return 0; + } + else + { + INF("efreet_menu_handle_move() unknown tag found " + "in Layout (%s)", child->tag); + return 0; + } + } + + return 1; +} + +/** + * @internal + * @param parent The parent menu + * @param xml The xml tree + * @return Returns 1 on success or 0 on failure + * @brief Handles the DefaultLayout tag + */ +static int +efreet_menu_handle_default_layout(Efreet_Menu_Internal *parent, Efreet_Xml *xml) +{ + const char *val; + Efreet_Xml *child; + Eina_List *l; + + if (!parent || !xml) return 0; + + /* We use the last existing layout */ + if (parent->default_layout) return 1; + + val = efreet_xml_attribute_get(xml, "show_empty"); + if (val) parent->show_empty = !strcmp(val, "true"); + + val = efreet_xml_attribute_get(xml, "inline"); + if (val) parent->in_line = !strcmp(val, "true"); + + val = efreet_xml_attribute_get(xml, "inline_limit"); + if (val) parent->inline_limit = atoi(val); + + val = efreet_xml_attribute_get(xml, "inline_header"); + if (val) parent->inline_header = !strcmp(val, "true"); + + val = efreet_xml_attribute_get(xml, "inline_alias"); + if (val) parent->inline_alias = !strcmp(val, "true"); + + efreet_menu_create_default_layout_list(parent); + + EINA_LIST_FOREACH(xml->children, l, child) + { + int (*cb)(Efreet_Menu_Internal *parent, Efreet_Xml *xml, int def); + + cb = eina_hash_find(efreet_menu_layout_cbs, child->tag); + if (cb) + { + if (!cb(parent, child, 1)) + return 0; + } + else + { + INF("efreet_menu_handle_move() unknown tag found in " + "DefaultLayout (%s)", child->tag); + return 0; + } + } + + return 1; +} + +static int +efreet_menu_handle_layout_menuname(Efreet_Menu_Internal *parent, Efreet_Xml *xml, int def) +{ + Efreet_Menu_Layout *layout; + const char *val; + + if (!parent || !xml) return 0; + + if (!xml->text) + { + INF("efreet_menu_handle_layout_menuname() The Menuname tag in " + "layout needs a filename."); + return 0; + } + + layout = efreet_menu_layout_new(); + layout->type = EFREET_MENU_LAYOUT_MENUNAME; + layout->name = eina_stringshare_add(xml->text); + + val = efreet_xml_attribute_get(xml, "show_empty"); + if (val) layout->show_empty = !strcmp(val, "true"); + + val = efreet_xml_attribute_get(xml, "inline"); + if (val) layout->in_line = !strcmp(val, "true"); + + val = efreet_xml_attribute_get(xml, "inline_limit"); + if (val) layout->inline_limit = atoi(val); + + val = efreet_xml_attribute_get(xml, "inline_header"); + if (val) layout->inline_header = !strcmp(val, "true"); + + val = efreet_xml_attribute_get(xml, "inline_alias"); + if (val) layout->inline_alias = !strcmp(val, "true"); + + if (def) parent->default_layout = eina_list_append(parent->default_layout, layout); + else parent->layout = eina_list_append(parent->layout, layout); + + return 1; +} + +static int +efreet_menu_handle_layout_filename(Efreet_Menu_Internal *parent, Efreet_Xml *xml, int def) +{ + Efreet_Menu_Layout *layout; + + if (!parent || !xml) return 0; + + if (!xml->text) + { + INF("efreet_menu_handle_layout_filename() The Filename tag in " + "layout needs a filename."); + return 0; + } + + layout = efreet_menu_layout_new(); + layout->type = EFREET_MENU_LAYOUT_FILENAME; + layout->name = eina_stringshare_add(xml->text); + + if (def) parent->default_layout = eina_list_append(parent->default_layout, layout); + else parent->layout = eina_list_append(parent->layout, layout); + + return 1; +} + +static int +efreet_menu_handle_layout_separator(Efreet_Menu_Internal *parent, Efreet_Xml *xml, int def) +{ + Efreet_Menu_Layout *layout; + + if (!parent || !xml) return 0; + + layout = efreet_menu_layout_new(); + layout->type = EFREET_MENU_LAYOUT_SEPARATOR; + if (def) + parent->default_layout = eina_list_append(parent->default_layout, layout); + else + parent->layout = eina_list_append(parent->layout, layout); + return 1; +} + +static int +efreet_menu_handle_layout_merge(Efreet_Menu_Internal *parent, Efreet_Xml *xml, int def) +{ + Efreet_Menu_Layout *layout; + const char *attr; + + if (!parent || !xml) return 0; + + attr = efreet_xml_attribute_get(xml, "type"); + if (!attr) + { + INF("efreet_menu_handle_layout_merge() The Merge tag in layout " + "needs a type attribute."); + return 0; + } + + if (strcmp(attr, "files") && strcmp(attr, "menus") && strcmp(attr, "all")) + { + INF("efreet_menu_handle_layout_merge() The type attribute for " + "the Merge tag contains an unknown value (%s).", attr); + return 0; + } + + layout = efreet_menu_layout_new(); + layout->type = EFREET_MENU_LAYOUT_MERGE; + layout->name = eina_stringshare_add(attr); + + if (def) parent->default_layout = eina_list_append(parent->default_layout, layout); + else parent->layout = eina_list_append(parent->layout, layout); + + return 1; +} + +/** + * @internal + * @param parent The parent menu + * @param xml The XML tree to parse + * @param type The type of filter + * @return Returns 1 on success or 0 on failure + * @brief Parses the given XML tree and adds the filter to the parent menu + */ +static int +efreet_menu_handle_filter(Efreet_Menu_Internal *parent, Efreet_Xml *xml, + Efreet_Menu_Filter_Type type) +{ + Efreet_Menu_Filter *filter; + + efreet_menu_create_filter_list(parent); + + /* filters have a default or relationship */ + filter = efreet_menu_filter_new(); + if (!filter) return 0; + filter->type = type; + filter->op->type = EFREET_MENU_FILTER_OP_OR; + + if (!efreet_menu_handle_filter_op(filter->op, xml)) + { + efreet_menu_filter_free(filter); + return 0; + } + + parent->filters = eina_list_prepend(parent->filters, filter); + + return 1; +} + +/** + * @internal + * @param op The operation to work with + * @param xml The XML tree representing this operation + * @return Returns 1 on success or 0 on failure + * @brief Parses the given XML tree and populates the operation + */ +static int +efreet_menu_handle_filter_op(Efreet_Menu_Filter_Op *op, Efreet_Xml *xml) +{ + Efreet_Xml *child; + Eina_List *l; + + EINA_LIST_FOREACH(xml->children, l, child) + { + int (*cb)(Efreet_Menu_Filter_Op *op, Efreet_Xml *xml); + + cb = eina_hash_find(efreet_menu_filter_cbs, child->tag); + if (cb) + { + if (!cb(op, child)) + return 0; + } + else + { + INF("efreet_menu_handle_filter_op() unknown tag in filter (%s)", child->tag); + return 0; + } + } + return 1; +} + +/** + * @internal + * @return Returns a new Efreet_Menu_Filter on success or NULL on failure + * @brief Creates and initializes an Efreet_Menu_Filter object + */ +static Efreet_Menu_Filter * +efreet_menu_filter_new(void) +{ + Efreet_Menu_Filter *filter; + + filter = NEW(Efreet_Menu_Filter, 1); + if (!filter) return NULL; + filter->op = efreet_menu_filter_op_new(); + if (!filter->op) + { + FREE(filter); + return NULL; + } + + return filter; +} + +/** + * @internal + * @param filter The filter to work with + * @return Returns no data + * @brief Frees the given filter and all data + */ +static void +efreet_menu_filter_free(Efreet_Menu_Filter *filter) +{ + if (!filter) return; + + if (filter->op) efreet_menu_filter_op_free(filter->op); + filter->op = NULL; + + FREE(filter); +} + +/** + * @internal + * @return Returns a new Efreet_Menu_Layout on success or NULL on failure + * @brief Creates and initializes an Efreet_Menu_Layout object + */ +static Efreet_Menu_Layout * +efreet_menu_layout_new(void) +{ + Efreet_Menu_Layout *layout; + + layout = NEW(Efreet_Menu_Layout, 1); + layout->show_empty = -1; + layout->in_line = -1; + layout->inline_limit = -1; + layout->inline_header = -1; + layout->inline_alias = -1; + + return layout; +} + +/** + * @internal + * @param filter The filter to work with + * @return Returns no data + * @brief Frees the given filter and all data + */ +static void +efreet_menu_layout_free(Efreet_Menu_Layout *layout) +{ + if (!layout) return; + + IF_RELEASE(layout->name); + FREE(layout); +} + +/** + * @internal + * @return Returns a new Efreet_Menu_Filter_Op on success or NULL on failure + * @brief Creates and initializes an Efreet_Menu_Filter_Op structure + */ +static Efreet_Menu_Filter_Op * +efreet_menu_filter_op_new(void) +{ + Efreet_Menu_Filter_Op *op; + + op = NEW(Efreet_Menu_Filter_Op, 1); + + return op; +} + +/** + * @internal + * @param op The operation to work with + * @return Returns no value. + * @brief Frees the given operation and all sub data + */ +static void +efreet_menu_filter_op_free(Efreet_Menu_Filter_Op *op) +{ + if (!op) return; + + IF_FREE_LIST(op->categories, eina_stringshare_del); + IF_FREE_LIST(op->filenames, eina_stringshare_del); + IF_FREE_LIST(op->filters, efreet_menu_filter_op_free); + + FREE(op); +} + +/** + * @internal + * @return Returns a new Efreet_Menu_Desktop on success or NULL on failure + * @brief Creates and returns an Efreet_Menu_Desktop + */ +static Efreet_Menu_Desktop * +efreet_menu_desktop_new(void) +{ + Efreet_Menu_Desktop *md; + + md = NEW(Efreet_Menu_Desktop, 1); + + return md; +} + +/** + * @internal + * @param md The Efreet_Menu_Desktop to free + * @return Returns no value + * @brief Frees the given structure + */ +static void +efreet_menu_desktop_free(Efreet_Menu_Desktop *md) +{ + IF_RELEASE(md->id); + if (md->desktop) efreet_desktop_free(md->desktop); + FREE(md); +} + +/** + * @internal + * @return Returns a new Efreet_Menu on success or NULL on failure + * @brief Creates and returns an Efreet_Menu + */ +static Efreet_Menu * +efreet_menu_entry_new(void) +{ + Efreet_Menu *entry; + + entry = NEW(Efreet_Menu, 1); + + return entry; +} + +EAPI void +efreet_menu_free(Efreet_Menu *entry) +{ + Efreet_Menu *sub; + + if (!entry) return; + + IF_RELEASE(entry->name); + IF_RELEASE(entry->icon); + EINA_LIST_FREE(entry->entries, sub) + efreet_menu_free(sub); + IF_RELEASE(entry->id); + if (entry->desktop) efreet_desktop_free(entry->desktop); + FREE(entry); +} + +/** + * @internal + * @param op The op to add a child too + * @param xml The XML tree of the child + * @param type The type of child to add + * @return Returns 1 on success or 0 on failure + * @brief Parses the given XML tree and populates a new child operation. + */ +static int +efreet_menu_handle_filter_child_op(Efreet_Menu_Filter_Op *op, Efreet_Xml *xml, + Efreet_Menu_Filter_Op_Type type) +{ + Efreet_Menu_Filter_Op *child_op; + + child_op = efreet_menu_filter_op_new(); + child_op->type = type; + + if (!efreet_menu_handle_filter_op(child_op, xml)) + { + efreet_menu_filter_op_free(child_op); + return 0; + } + + op->filters = eina_list_append(op->filters, child_op); + + return 1; +} + +/** + * @internal + * @param menu The menu to work with + * @param only_unallocated Do we only look for unallocated items? + * @return Returns 1 if we've successfully processed the menu, 0 otherwise + * @brief Handles the processing of the menu data to retrieve the .desktop + * files for the menu + */ +static int +efreet_menu_process(Efreet_Menu_Internal *internal, unsigned int only_unallocated) +{ + Eina_List *l; + + /* a menu _MUST_ have a name */ + if (!internal->name.internal || (internal->name.internal[0] == '\0')) + return 0; + + /* handle filtering out .desktop files as needed. This deals with all + * .desktop files */ + efreet_menu_process_filters(internal, only_unallocated); + + if (internal->sub_menus) + { + Efreet_Menu_Internal *sub_internal; + + EINA_LIST_FOREACH(internal->sub_menus, l, sub_internal) + { + sub_internal->parent = internal; + efreet_menu_process(sub_internal, only_unallocated); + } + } + + return 1; +} + +/* This will walk through all of the app dirs and load all the .desktop + * files into the cache for the menu. The .desktop files will have their + * allocated flag set to 0 */ +static int +efreet_menu_process_dirs(Efreet_Menu_Internal *internal) +{ + Eina_List *l; + + /* Scan application directories for .desktop files */ + if (!efreet_menu_app_dirs_process(internal)) + return 0; + + /* Scan directory directories for .directory file */ + if (!efreet_menu_directory_dirs_process(internal)) + return 0; + + if (internal->sub_menus) + { + Efreet_Menu_Internal *sub_internal; + + EINA_LIST_FOREACH(internal->sub_menus, l, sub_internal) + { + sub_internal->parent = internal; + efreet_menu_process_dirs(sub_internal); + } + } + + return 1; +} + +/** + * @internal + * @param menu the menu to process + * @param only_unallocated Only handle menus that deal with unallocated items + * @return Returns no value + * @brief Handles the processing of the filters attached to the given menu. + * + * For each include filter we'll add the items to our applications array. Each + * exclude filter will remove items from the applications array + */ +static void +efreet_menu_process_filters(Efreet_Menu_Internal *internal, unsigned int only_unallocated) +{ + Efreet_Menu_Filter *filter; + Efreet_Menu_Desktop *md; + Eina_List *l, *ll; + + int included = 0; + + /* nothing to do if we're checking the other option */ + if (only_unallocated != internal->only_unallocated) return; + + internal->applications = eina_list_free(internal->applications); + + if (!internal->filters) return; + + EINA_LIST_FOREACH(internal->filters, l, filter) + { + /* skip excludes until we get an include */ + if (!included && (filter->type == EFREET_MENU_FILTER_EXCLUDE)) + continue; + included = 1; + + if (filter->type == EFREET_MENU_FILTER_INCLUDE) + { + Eina_Hash *matches; + + matches = eina_hash_string_superfast_new(NULL); + internal->applications = efreet_menu_process_app_pool(internal->app_pool, internal->applications, + matches, filter, internal->only_unallocated); + if (internal->parent) + { + Efreet_Menu_Internal *parent; + + parent = internal->parent; + do { + internal->applications = efreet_menu_process_app_pool(parent->app_pool, + internal->applications, matches, filter, + internal->only_unallocated); + } while ((parent = parent->parent)); + } + eina_hash_free(matches); + } + else + { + /* check each item in our menu so far and see if it's excluded */ + l = internal->applications; + while ((md = eina_list_data_get(l))) + { + ll = eina_list_next(l); + if (efreet_menu_filter_matches(filter->op, md)) + internal->applications = eina_list_remove_list(internal->applications, l); + l = ll; + } + } + } + + /* sort the menu applications. we do this in process filters so it will only + * be done once per menu.*/ + if (eina_list_count(internal->applications)) + { + Eina_List *l2; + + EINA_LIST_FOREACH_SAFE(internal->applications, l, l2, md) + { + if (md->desktop->no_display) + internal->applications = eina_list_remove_list(internal->applications, l); + } + internal->applications = eina_list_sort(internal->applications, + eina_list_count(internal->applications), + EINA_COMPARE_CB(efreet_menu_cb_md_compare)); + } +} + +/** + * @internal + * @param pool The app pool to iterate + * @param applications The list of applications to append too + * @param matches The hash of previously matched ids + * @param filter The menu filter to run on the pool items + * @param only_unallocated Do we check only unallocated pool items? + * @return Returns no value. + * @brief This will iterate the items in @a pool and append them to @a + * applications if they match the @a filter given and aren't previoulsy entered + * in @a matches. If @a only_unallocated is set we'll only only at the + * .desktop files that haven't been previoulsy matched + */ +static Eina_List * +efreet_menu_process_app_pool(Eina_List *pool, Eina_List *applications, + Eina_Hash *matches, Efreet_Menu_Filter *filter, + unsigned int only_unallocated) +{ + Efreet_Menu_Desktop *md; + Eina_List *l; + + EINA_LIST_FOREACH(pool, l, md) + { + if (eina_hash_find(matches, md->id)) continue; + if (only_unallocated && md->allocated) continue; + if (efreet_menu_filter_matches(filter->op, md)) + { + applications = eina_list_append(applications, md); + eina_hash_direct_add(matches, (void *)md->id, md); + md->allocated = 1; + } + } + return applications; +} + +/** + * @internal + * @param op The filter operation to execute + * @param md The desktop to run the filter on + * @return Returns 1 if this desktop matches the given filter, 0 otherwise + * @brief This will execute the given @a filter on the given desktop + */ +static int +efreet_menu_filter_matches(Efreet_Menu_Filter_Op *op, Efreet_Menu_Desktop *md) +{ + if (op->type == EFREET_MENU_FILTER_OP_OR) + return efreet_menu_filter_or_matches(op, md); + + if (op->type == EFREET_MENU_FILTER_OP_AND) + return efreet_menu_filter_and_matches(op, md); + + if (op->type == EFREET_MENU_FILTER_OP_NOT) + return efreet_menu_filter_not_matches(op, md); + + return 0; +} + +/** + * @internal + * @param op The filter operation to execute + * @param md The desktop to execute on + * @return Returns 1 if the desktop matches, 0 otherwise + * @brief Executes the OR operation, @a op, on the desktop, @a md. + */ +static int +efreet_menu_filter_or_matches(Efreet_Menu_Filter_Op *op, Efreet_Menu_Desktop *md) +{ + Efreet_Menu_Filter_Op *child; + Eina_List *l; + char *t; + + if (op->all) return 1; + + if (op->categories && md->desktop->categories) + { + EINA_LIST_FOREACH(op->categories, l, t) + { + if (eina_list_search_unsorted(md->desktop->categories, + EINA_COMPARE_CB(strcmp), t)) + return 1; + } + } + + if (op->filenames) + { + EINA_LIST_FOREACH(op->filenames, l, t) + if (t == md->id) return 1; + } + + if (op->filters) + { + EINA_LIST_FOREACH(op->filters, l, child) + { + if (efreet_menu_filter_matches(child, md)) + return 1; + } + } + + return 0; +} + +/** + * @internal + * @param op The filter operation to execute + * @param md The desktop to execute on + * @return Returns 1 if the desktop matches, 0 otherwise + * @brief Executes the AND operation, @a op, on the desktop, @a md. + */ +static int +efreet_menu_filter_and_matches(Efreet_Menu_Filter_Op *op, Efreet_Menu_Desktop *md) +{ + Efreet_Menu_Filter_Op *child; + Eina_List *l; + char *t; + + if (op->categories) + { + if ((eina_list_count(op->categories) > 0) && !md->desktop->categories) + return 0; + + EINA_LIST_FOREACH(op->categories, l, t) + { + if (!eina_list_search_unsorted(md->desktop->categories, + EINA_COMPARE_CB(strcmp), t)) + return 0; + } + } + + if (op->filenames) + { + EINA_LIST_FOREACH(op->filenames, l, t) + { + if (t != md->id) return 0; + } + } + + if (op->filters) + { + EINA_LIST_FOREACH(op->filters, l, child) + { + if (!efreet_menu_filter_matches(child, md)) + return 0; + } + } + + return 1; +} + +/** + * @internal + * @param op The filter operation to execute + * @param md The desktop to execute on + * @return Returns 1 if the desktop matches, 0 otherwise + * @brief Executes the NOT operation, @a op, on the desktop, @a md. + */ +static int +efreet_menu_filter_not_matches(Efreet_Menu_Filter_Op *op, Efreet_Menu_Desktop *md) +{ + Efreet_Menu_Filter_Op *child; + Eina_List *l; + char *t; + + /* !all means no desktops match */ + if (op->all) return 0; + + if (op->categories) + { + if ((eina_list_count(op->categories) > 0) && !md->desktop->categories) + return 1; + + EINA_LIST_FOREACH(op->categories, l, t) + { + if (eina_list_search_unsorted(md->desktop->categories, + EINA_COMPARE_CB(strcmp), t)) + return 0; + } + } + + if (op->filenames) + { + EINA_LIST_FOREACH(op->filenames, l, t) + { + if (t == md->id) return 0; + } + } + + if (op->filters) + { + EINA_LIST_FOREACH(op->filters, l, child) + { + if (efreet_menu_filter_matches(child, md)) + return 0; + } + } + + return 1; +} + +/** + * @internal + * @param dest The destination menu + * @param src The source menu + * @return Returns no value + * @brief Takes the child elements of the menu @a src and puts then on the + * _start_ of the menu @a dest. + */ +static void +efreet_menu_concatenate(Efreet_Menu_Internal *dest, Efreet_Menu_Internal *src) +{ + Efreet_Menu_Internal *submenu; + + if (!dest || !src) return; + + if (!dest->directory && src->directory) + { + dest->directory = src->directory; + src->directory = NULL; + } + + if (!dest->seen_allocated && src->seen_allocated) + { + dest->only_unallocated = src->only_unallocated; + dest->seen_allocated = 1; + } + + if (!dest->seen_deleted && src->seen_deleted) + { + dest->deleted = src->deleted; + dest->seen_deleted = 1; + } + + if (src->directories) + { + efreet_menu_create_directories_list(dest); + dest->directories = eina_list_merge(src->directories, dest->directories); + src->directories = NULL; + } + + if (src->app_dirs) + { + efreet_menu_create_app_dirs_list(dest); + dest->app_dirs = eina_list_merge(src->app_dirs, dest->app_dirs); + src->app_dirs = NULL; + } + + if (src->directory_dirs) + { + efreet_menu_create_directory_dirs_list(dest); + dest->directory_dirs = eina_list_merge(src->directory_dirs, dest->directory_dirs); + src->directory_dirs = NULL; + } + + if (src->moves) + { + efreet_menu_create_move_list(dest); + dest->moves = eina_list_merge(src->moves, dest->moves); + src->moves = NULL; + } + + if (src->filters) + { + efreet_menu_create_filter_list(dest); + dest->filters = eina_list_merge(src->filters, dest->filters); + src->filters = NULL; + } + + if (src->sub_menus) + { + efreet_menu_create_sub_menu_list(dest); + + while ((submenu = eina_list_data_get(eina_list_last(src->sub_menus)))) + { + Efreet_Menu_Internal *match; + + src->sub_menus = eina_list_remove(src->sub_menus, submenu); + /* if this menu is in the list already we just add to that */ + if ((match = eina_list_search_unsorted(dest->sub_menus, + EINA_COMPARE_CB(efreet_menu_cb_menu_compare), + submenu))) + { + efreet_menu_concatenate(match, submenu); + efreet_menu_internal_free(submenu); + } + else + dest->sub_menus = eina_list_prepend(dest->sub_menus, submenu); + } + } +} + +/** + * @internal + * @param menu The menu to work with + * @return Returns no value + * @brief Handles any \ commands in the menus + */ +static void +efreet_menu_resolve_moves(Efreet_Menu_Internal *internal) +{ + Efreet_Menu_Internal *child; + Efreet_Menu_Move *move; + Eina_List *l; + + /* child moves are handled before parent moves */ + if (internal->sub_menus) + { + EINA_LIST_FOREACH(internal->sub_menus, l, child) + efreet_menu_resolve_moves(child); + } + + /* nothing to do if this menu has no moves */ + if (!internal->moves) return; + + EINA_LIST_FOREACH(internal->moves, l, move) + { + Efreet_Menu_Internal *origin, *dest, *parent; + + /* if the origin path doesn't exist we do nothing */ + origin = efreet_menu_by_name_find(internal, move->old_name, &parent); + if (!origin) continue; + + /* remove the origin menu from the parent */ + parent->sub_menus = eina_list_remove(parent->sub_menus, origin); + + /* if the destination path doesn't exist we just rename the origin + * menu and append to the parents list of children */ + dest = efreet_menu_by_name_find(internal, move->new_name, &parent); + if (!dest) + { + char *path, *tmp, *t; + size_t len; + + /* if the dest path has /'s in it then we need to add menus to + * fill out the paths */ + len = strlen(move->new_name) + 1; + t = alloca(len); + memcpy(t, move->new_name, len); + tmp = t; + path = strchr(tmp, '/'); + while (path) + { + Efreet_Menu_Internal *ancestor; + + *path = '\0'; + + ancestor = efreet_menu_internal_new(); + if (!ancestor) goto error; + ancestor->name.internal = eina_stringshare_add(tmp); + + efreet_menu_create_sub_menu_list(parent); + parent->sub_menus = eina_list_append(parent->sub_menus, ancestor); + + parent = ancestor; + tmp = ++path; + path = strchr(tmp, '/'); + } + + IF_RELEASE(origin->name.internal); + origin->name.internal = eina_stringshare_add(tmp); + + efreet_menu_create_sub_menu_list(parent); + parent->sub_menus = eina_list_append(parent->sub_menus, origin); + } + else + { + efreet_menu_concatenate(dest, origin); + efreet_menu_internal_free(origin); + } + } +error: + IF_FREE_LIST(internal->moves, efreet_menu_move_free); +} + +/** + * @internal + * @param menu The menu to start searching from + * @param name The menu name to find + * @param parent The parent of the found menu + * @return Returns the menu with the given @a name or NULL if none found + * @brief Searches the menu tree starting at @a menu looking for a menu with + * @a name. + */ +static Efreet_Menu_Internal * +efreet_menu_by_name_find(Efreet_Menu_Internal *internal, const char *name, Efreet_Menu_Internal **parent) +{ + char *part, *tmp, *ptr; + size_t len; + + if (parent) *parent = internal; + + /* find the correct parent menu */ + len = strlen(name) + 1; + tmp = alloca(len); + memcpy(tmp, name, len); + ptr = tmp; + part = strchr(ptr, '/'); + while (part) + { + *part = '\0'; + + if (!(internal = eina_list_search_unsorted(internal->sub_menus, + EINA_COMPARE_CB(efreet_menu_cb_compare_names), + ptr))) + { + return NULL; + } + + ptr = ++part; + part = strchr(ptr, '/'); + } + + if (parent) *parent = internal; + + /* find the menu in the parent list */ + if (!(internal = eina_list_search_unsorted(internal->sub_menus, + EINA_COMPARE_CB(efreet_menu_cb_compare_names), + ptr))) + { + return NULL; + } + + return internal; +} + +static void +efreet_menu_path_set(Efreet_Menu_Internal *internal, const char *path) +{ + char *tmp, *p; + size_t len; + + len = strlen(path) + 1; + tmp = alloca(len); + memcpy(tmp, path, len); + p = strrchr(tmp, '/'); + if (p) + { + *p = '\0'; + p++; + + internal->file.path = eina_stringshare_add(tmp); + internal->file.name = eina_stringshare_add(p); + } +} + +/** + * @internal + * @return Returns a new Efreet_Menu_Move struct on success or NULL on failure + * @brief Creates an returns a new Efreet_Menu_Move struct or NULL on failure + */ +static Efreet_Menu_Move * +efreet_menu_move_new(void) +{ + Efreet_Menu_Move *move; + + move = NEW(Efreet_Menu_Move, 1); + + return move; +} + +/** + * @internal + * @param move The Efreet_Menu_Move to free + * @return Returns no value. + * @brief Frees the given move structure + */ +static void +efreet_menu_move_free(Efreet_Menu_Move *move) +{ + if (!move) return; + + IF_RELEASE(move->old_name); + IF_RELEASE(move->new_name); + + FREE(move); +} + +/** + * @internal + * @return Returns a new Efreet_Menu_App_Dir on success or NULL on failure + * @brief Creates and initializes a new Efreet_Menu_App_Dir structure + */ +static Efreet_Menu_App_Dir * +efreet_menu_app_dir_new(void) +{ + Efreet_Menu_App_Dir *dir; + + dir = NEW(Efreet_Menu_App_Dir, 1); + + return dir; +} + +/** + * @internal + * @param dir The Efreet_Menu_App_Dir to free + * @return Returns no value. + * @brief Frees the given dir structure + */ +static void +efreet_menu_app_dir_free(Efreet_Menu_App_Dir *dir) +{ + if (!dir) return; + + IF_RELEASE(dir->path); + IF_RELEASE(dir->prefix); + FREE(dir); +} + +/** + * @internal + * @param a The app dir to compare too + * @param b The path to compare too + * @return Returns 0 if the strings are equals, != 0 otherwise + * @brief Compares the too strings + */ +static int +efreet_menu_cb_app_dirs_compare(Efreet_Menu_App_Dir *a, const char *b) +{ + if (!a->path || !b) return 1; + if (a->path == b) return 0; + return strcmp(a->path, b); +} + +static void +efreet_menu_create_sub_menu_list(Efreet_Menu_Internal *internal) +{ + if (!internal || internal->sub_menus) return; + + internal->sub_menus = NULL; +} + +static void +efreet_menu_create_app_dirs_list(Efreet_Menu_Internal *internal) +{ + if (!internal || internal->app_dirs) return; + + internal->app_dirs = NULL; +} + +static void +efreet_menu_create_directory_dirs_list(Efreet_Menu_Internal *internal) +{ + if (!internal || internal->directory_dirs) return; + + internal->directory_dirs = NULL; +} + +static void +efreet_menu_create_move_list(Efreet_Menu_Internal *internal) +{ + if (!internal || internal->moves) return; + + internal->moves = NULL; +} + +static void +efreet_menu_create_filter_list(Efreet_Menu_Internal *internal) +{ + if (!internal || internal->filters) return; + + internal->filters = NULL; +} + +static void +efreet_menu_create_layout_list(Efreet_Menu_Internal *internal) +{ + if (!internal || internal->layout) return; + + internal->layout = NULL; +} + +static void +efreet_menu_create_default_layout_list(Efreet_Menu_Internal *internal) +{ + if (!internal || internal->default_layout) return; + + internal->default_layout = NULL; +} + +static void +efreet_menu_create_directories_list(Efreet_Menu_Internal *internal) +{ + if (!internal || internal->directories) return; + + internal->directories = NULL; +} + +static const char * +efreet_menu_path_get(Efreet_Menu_Internal *internal, const char *suffix) +{ + char path[PATH_MAX]; + size_t len; + + /* see if we've got an absolute or relative path */ + if (suffix[0] == '/') + snprintf(path, sizeof(path), "%s", suffix); + + else + { + if (!internal->file.path) + { + INF("efreet_menu_handle_app_dir() missing menu path ..."); + return NULL; + } + snprintf(path, sizeof(path), "%s/%s", internal->file.path, suffix); + } + + len = strlen(path); + while (path[len] == '/') path[len--] = '\0'; + + return eina_stringshare_add(path); +} + +static int +efreet_menu_cb_menu_compare(Efreet_Menu_Internal *a, Efreet_Menu_Internal *b) +{ + if (!a->name.internal || !b->name.internal) return 1; + if (a->name.internal == b->name.internal) return 0; + return strcmp(a->name.internal, b->name.internal); +} + +static int +efreet_menu_app_dirs_process(Efreet_Menu_Internal *internal) +{ + Efreet_Menu_App_Dir *app_dir; + Efreet_Menu_Desktop *md; + Eina_List *l; + + EINA_LIST_FREE(internal->app_pool, md) + efreet_menu_desktop_free(md); + + EINA_LIST_FOREACH(internal->app_dirs, l, app_dir) + efreet_menu_app_dir_scan(internal, app_dir->path, app_dir->prefix, app_dir->legacy); + + return 1; +} + +static int +efreet_menu_app_dir_scan(Efreet_Menu_Internal *internal, const char *path, const char *id, int legacy) +{ + Efreet_Desktop *desktop; + Efreet_Menu_Desktop *menu_desktop; + char buf2[PATH_MAX]; + Eina_Iterator *it; + Eina_File_Direct_Info *info; + + it = eina_file_direct_ls(path); + if (!it) return 1; + + EINA_ITERATOR_FOREACH(it, info) + { + const char *fname; + + fname = info->path + info->name_start; + if (id) + snprintf(buf2, sizeof(buf2), "%s-%s", id, fname); + else + strcpy(buf2, fname); + + if (ecore_file_is_dir(info->path)) + { + if (!legacy) + efreet_menu_app_dir_scan(internal, info->path, buf2, legacy); + } + else + { + const char *ext; + + ext = strrchr(fname, '.'); + + if (!ext || strcmp(ext, ".desktop")) continue; + desktop = efreet_desktop_get(info->path); + + if (!desktop || desktop->type != EFREET_DESKTOP_TYPE_APPLICATION) + { + if (desktop) efreet_desktop_free(desktop); + continue; + } + /* Don't add two files with the same id in the app pool */ + if (eina_list_search_unsorted(internal->app_pool, + EINA_COMPARE_CB(efreet_menu_cb_md_compare_ids), + buf2)) + { + if (desktop) efreet_desktop_free(desktop); + continue; + } + + menu_desktop = efreet_menu_desktop_new(); + menu_desktop->desktop = desktop; + menu_desktop->id = eina_stringshare_add(buf2); + internal->app_pool = eina_list_prepend(internal->app_pool, menu_desktop); + } + } + eina_iterator_free(it); + + return 1; +} + +/** + * @internal + * @param menu The menu to work with + * @return Returns 1 on success or 0 on failure + * @brief Process the directory dirs in @a menu + */ +static int +efreet_menu_directory_dirs_process(Efreet_Menu_Internal *internal) +{ + const char *path; + Eina_List *l; + + if (internal->directory_dirs) + { + internal->directory_cache = + eina_hash_string_superfast_new(EINA_FREE_CB(efreet_desktop_free)); + + EINA_LIST_REVERSE_FOREACH(internal->directory_dirs, l, path) + efreet_menu_directory_dir_scan(path, NULL, internal->directory_cache); + } + + if (internal->directories) + { + EINA_LIST_REVERSE_FOREACH(internal->directories, l, path) + { + internal->directory = efreet_menu_directory_get(internal, path); + if (internal->directory) break; + } + } + if (!internal->directory) + internal->name.name = internal->name.internal; + else + internal->name.name = internal->directory->name; + + return 1; +} + +/** + * @internal + * @param path The path to scan + * @param relative_path The relative portion of the path + * @param cache The cache to populate + * @return Returns 1 on success or 0 on failure + * @brief Scans the given directory dir for .directory files and adds the + * applications to the cache + */ +static int +efreet_menu_directory_dir_scan(const char *path, const char *relative_path, + Eina_Hash *cache) +{ + Efreet_Desktop *desktop; + char buf2[PATH_MAX]; + Eina_Iterator *it; + Eina_File_Direct_Info *info; + char *ext; + + it = eina_file_direct_ls(path); + if (!it) return 1; + + EINA_ITERATOR_FOREACH(it, info) + { + const char *fname; + + fname = info->path + info->name_start; + if (relative_path) + snprintf(buf2, sizeof(buf2), "%s/%s", relative_path, fname); + else + strcpy(buf2, fname); + + if (ecore_file_is_dir(info->path)) + efreet_menu_directory_dir_scan(info->path, buf2, cache); + + else + { + ext = strrchr(fname, '.'); + if (!ext || strcmp(ext, ".directory")) continue; + + desktop = efreet_desktop_get(info->path); + if (!desktop || desktop->type != EFREET_DESKTOP_TYPE_DIRECTORY) + { + efreet_desktop_free(desktop); + continue; + } + + eina_hash_del(cache, buf2, NULL); + eina_hash_add(cache, buf2, desktop); + } + } + eina_iterator_free(it); + + return 1; +} + +/** + * @internal + * @param menu The menu to work with + * @param path The path to work with + * @return Returns the desktop file for this path or NULL if none exists + * @brief Finds the desktop file for the given path. + */ +static Efreet_Desktop * +efreet_menu_directory_get(Efreet_Menu_Internal *internal, const char *path) +{ + Efreet_Desktop *dir; + + if (internal->directory_cache) + { + dir = eina_hash_find(internal->directory_cache, path); + if (dir) return dir; + } + + if (internal->parent) + return efreet_menu_directory_get(internal->parent, path); + + return NULL; +} + +/** + * @internal + * @param a The first desktop + * @param b The second desktop + * @return Returns the comparison of the desktop files + * @brief Compares the desktop files. + */ +static int +efreet_menu_cb_md_compare(const Efreet_Menu_Desktop *a, const Efreet_Menu_Desktop *b) +{ +#ifdef STRICT_SPEC + return strcmp(ecore_file_file_get(a->desktop->orig_path), ecore_file_file_get(b->desktop->orig_path)); +#else + if (a->desktop->name == b->desktop->name) return 0; + return strcasecmp(a->desktop->name, b->desktop->name); +#endif +} + +static int +efreet_menu_cb_compare_names(Efreet_Menu_Internal *internal, const char *name) +{ + if (internal->name.internal == name) return 0; + return strcmp(internal->name.internal, name); +} + +static int +efreet_menu_cb_md_compare_ids(Efreet_Menu_Desktop *md, const char *name) +{ + if (md->id == name) return 0; + return strcmp(md->id, name); +} + +static Efreet_Menu * +efreet_menu_layout_menu(Efreet_Menu_Internal *internal) +{ + Efreet_Menu *entry; + Eina_List *layout = NULL; + Eina_List *l; + + if (internal->parent) + { + /* Copy default layout rules */ + if (internal->show_empty == -1) internal->show_empty = internal->parent->show_empty; + if (internal->in_line == -1) internal->in_line = internal->parent->in_line; + if (internal->inline_limit == -1) internal->inline_limit = internal->parent->inline_limit; + if (internal->inline_header == -1) internal->inline_header = internal->parent->inline_header; + if (internal->inline_alias == -1) internal->inline_alias = internal->parent->inline_alias; + } + + if (internal->layout) + layout = internal->layout; + + else if (internal->parent) + { + Efreet_Menu_Internal *parent; + parent = internal->parent; + do + { + layout = parent->default_layout; + parent = parent->parent; + } while (!layout && parent); + } + + /* init entry */ + entry = efreet_menu_entry_new(); + entry->type = EFREET_MENU_ENTRY_MENU; + entry->id = eina_stringshare_add(internal->name.internal); + entry->name = eina_stringshare_add(internal->name.name); + if (internal->directory) + { + entry->icon = eina_stringshare_add(internal->directory->icon); + efreet_desktop_ref(internal->directory); + entry->desktop = internal->directory; + } + entry->entries = NULL; + +#if 1 //STRICT_SPEC + if (internal->sub_menus) + { + internal->sub_menus = eina_list_sort(internal->sub_menus, + 0, + EINA_COMPARE_CB(efreet_menu_cb_menu_compare)); + } +#endif + + if (layout) + { + Efreet_Menu_Layout *lay; + + EINA_LIST_FOREACH(layout, l, lay) + efreet_menu_layout_entries_get(entry, internal, lay); + } + else + { + /* Default layout, first menus, then desktop */ + if (internal->sub_menus) + { + Efreet_Menu_Internal *sub; + + EINA_LIST_FOREACH(internal->sub_menus, l, sub) + { + Efreet_Menu *sub_entry; + if ((sub->directory && sub->directory->no_display) || sub->deleted) continue; + sub_entry = efreet_menu_layout_menu(sub); + /* Don't show empty menus */ + if (!sub_entry->entries) + { + efreet_menu_free(sub_entry); + continue; + } + entry->entries = eina_list_append(entry->entries, sub_entry); + } + } + + if (internal->applications) + { + Efreet_Menu_Desktop *md; + + EINA_LIST_FOREACH(internal->applications, l, md) + { + Efreet_Menu *sub_entry; + sub_entry = efreet_menu_layout_desktop(md); + entry->entries = eina_list_append(entry->entries, sub_entry); + } + } + } + + /* Don't keep this list around if it is empty */ + + return entry; +} + +static Efreet_Menu * +efreet_menu_layout_desktop(Efreet_Menu_Desktop *md) +{ + Efreet_Menu *entry; + + /* init entry */ + entry = efreet_menu_entry_new(); + entry->type = EFREET_MENU_ENTRY_DESKTOP; + entry->id = eina_stringshare_add(md->id); + entry->name = eina_stringshare_add(md->desktop->name); + if (md->desktop->icon) entry->icon = eina_stringshare_add(md->desktop->icon); + efreet_desktop_ref(md->desktop); + entry->desktop = md->desktop; + + return entry; +} + +static void +efreet_menu_layout_entries_get(Efreet_Menu *entry, Efreet_Menu_Internal *internal, + Efreet_Menu_Layout *layout) +{ + Efreet_Menu *sub_entry; + + if (internal->sub_menus && layout->type == EFREET_MENU_LAYOUT_MENUNAME) + { + Efreet_Menu_Internal *sub; + + /* Efreet_Menu_Layout might be from DefaultLayout, so we need a local copy */ + int show_empty, in_line, inline_limit, inline_header, inline_alias; + + if (layout->show_empty == -1) show_empty = internal->show_empty; + else show_empty = layout->show_empty; + + if (layout->in_line == -1) in_line = internal->in_line; + else in_line = layout->in_line; + + if (layout->inline_limit == -1) inline_limit = internal->inline_limit; + else inline_limit = layout->inline_limit; + + if (layout->inline_header == -1) inline_header = internal->inline_header; + else inline_header = layout->inline_header; + + if (layout->inline_alias == -1) inline_alias = internal->inline_alias; + else inline_alias = layout->inline_alias; + + sub = eina_list_search_unsorted(internal->sub_menus, + EINA_COMPARE_CB(efreet_menu_cb_compare_names), layout->name); + if (sub) + { + if (!(sub->directory && sub->directory->no_display) && !sub->deleted) + { + sub_entry = efreet_menu_layout_menu(sub); + if (!show_empty && efreet_menu_layout_is_empty(sub_entry)) + efreet_menu_free(sub_entry); + else if (in_line && + ((inline_limit == 0) || + (!sub_entry->entries || + (inline_limit > 0 && eina_list_count(sub_entry->entries) <= (unsigned int)inline_limit)))) + { + /* Inline */ + if (!sub_entry->entries) + { + /* Can't inline an empty submenu */ + entry->entries = eina_list_append(entry->entries, sub_entry); + } + else if (inline_alias && (eina_list_count(sub_entry->entries) == 1)) + { + Efreet_Menu *tmp; + + tmp = eina_list_data_get(sub_entry->entries); + sub_entry->entries = eina_list_remove_list(sub_entry->entries, sub_entry->entries); + IF_RELEASE(tmp->name); + tmp->name = sub_entry->name; + sub_entry->name = NULL; + IF_RELEASE(tmp->icon); + tmp->icon = sub_entry->icon; + sub_entry->icon = NULL; + entry->entries = eina_list_append(entry->entries, tmp); + efreet_menu_free(sub_entry); + } + else + { + Efreet_Menu *tmp; + + if (inline_header) + { + tmp = efreet_menu_entry_new(); + tmp->type = EFREET_MENU_ENTRY_HEADER; + tmp->name = sub_entry->name; + sub_entry->name = NULL; + tmp->icon = sub_entry->icon; + sub_entry->icon = NULL; + entry->entries = eina_list_append(entry->entries, tmp); + } + while ((tmp = eina_list_data_get(sub_entry->entries))) + { + sub_entry->entries = eina_list_remove_list(sub_entry->entries, sub_entry->entries); + entry->entries = eina_list_append(entry->entries, tmp); + } + efreet_menu_free(sub_entry); + } + } + else + entry->entries = eina_list_append(entry->entries, sub_entry); + } + internal->sub_menus = eina_list_remove(internal->sub_menus, sub); + efreet_menu_internal_free(sub); + } + } + else if (internal->applications && layout->type == EFREET_MENU_LAYOUT_FILENAME) + { + Efreet_Menu_Desktop *md; + md = eina_list_search_unsorted(internal->applications, + EINA_COMPARE_CB(efreet_menu_cb_md_compare_ids), layout->name); + if (md) + { + sub_entry = efreet_menu_layout_desktop(md); + entry->entries = eina_list_append(entry->entries, sub_entry); + internal->applications = eina_list_remove(internal->applications, md); + } + } + else if (layout->type == EFREET_MENU_LAYOUT_MERGE) + { + if (internal->applications && !strcmp(layout->name, "files")) + { + Efreet_Menu_Desktop *md; + + while ((md = eina_list_data_get(internal->applications))) + { + internal->applications = eina_list_remove_list(internal->applications, + internal->applications); + sub_entry = eina_list_search_unsorted(entry->entries, + EINA_COMPARE_CB(efreet_menu_cb_entry_compare_desktop), + md->desktop); + if (!sub_entry) + { + sub_entry = efreet_menu_layout_desktop(md); + entry->entries = eina_list_append(entry->entries, sub_entry); + } + } + internal->applications = eina_list_free(internal->applications); + } + else if (internal->sub_menus && !strcmp(layout->name, "menus")) + { + Efreet_Menu_Internal *sub; + + while ((sub = eina_list_data_get(internal->sub_menus))) + { + internal->sub_menus = eina_list_remove_list(internal->sub_menus, internal->sub_menus); + if ((sub->directory && sub->directory->no_display) || sub->deleted) + { + efreet_menu_internal_free(sub); + continue; + } + sub_entry = eina_list_search_unsorted(entry->entries, + EINA_COMPARE_CB(efreet_menu_cb_entry_compare_menu), + sub); + if (!sub_entry) + { + sub_entry = efreet_menu_layout_menu(sub); + if (!internal->show_empty && efreet_menu_layout_is_empty(sub_entry)) + efreet_menu_free(sub_entry); + else if (internal->in_line && + ((internal->inline_limit == 0) || + (!sub_entry->entries || + (internal->inline_limit > 0 && eina_list_count(sub_entry->entries) <= (unsigned int)internal->inline_limit)))) + { + /* Inline */ + if (!sub_entry->entries) + { + /* Can't inline an empty submenu */ + entry->entries = eina_list_append(entry->entries, sub_entry); + } + else if (internal->inline_alias && (eina_list_count(sub_entry->entries) == 1)) + { + Efreet_Menu *tmp; + + tmp = eina_list_data_get(sub_entry->entries); + sub_entry->entries = eina_list_remove_list(sub_entry->entries, sub_entry->entries); + eina_stringshare_del(tmp->name); + tmp->name = sub_entry->name; + sub_entry->name = NULL; + IF_RELEASE(tmp->icon); + if (sub_entry->icon) + { + tmp->icon = sub_entry->icon; + sub_entry->icon = NULL; + } + entry->entries = eina_list_append(entry->entries, tmp); + efreet_menu_free(sub_entry); + } + else + { + Efreet_Menu *tmp; + + if (internal->inline_header) + { + tmp = efreet_menu_entry_new(); + tmp->type = EFREET_MENU_ENTRY_HEADER; + tmp->name = sub_entry->name; + sub_entry->name = NULL; + if (sub_entry->icon) tmp->icon = sub_entry->icon; + sub_entry->icon = NULL; + entry->entries = eina_list_append(entry->entries, tmp); + } + while ((tmp = eina_list_data_get(sub_entry->entries))) + { + sub_entry->entries = eina_list_remove_list(sub_entry->entries, + sub_entry->entries); + entry->entries = eina_list_append(entry->entries, tmp); + } + efreet_menu_free(sub_entry); + } + } + else + entry->entries = eina_list_append(entry->entries, sub_entry); + } + efreet_menu_internal_free(sub); + } + IF_FREE_LIST(internal->sub_menus, efreet_menu_internal_free); + } + else if (internal->sub_menus && !strcmp(layout->name, "all")) + { + const char *orig; + + orig = layout->name; + layout->name = "menus"; + efreet_menu_layout_entries_get(entry, internal, layout); + layout->name = "files"; + efreet_menu_layout_entries_get(entry, internal, layout); + layout->name = orig; + } + } + else if (layout->type == EFREET_MENU_LAYOUT_SEPARATOR) + { + sub_entry = efreet_menu_entry_new(); + sub_entry->type = EFREET_MENU_ENTRY_SEPARATOR; + entry->entries = eina_list_append(entry->entries, sub_entry); + } +} + +static int +efreet_menu_cb_entry_compare_menu(Efreet_Menu *entry, Efreet_Menu_Internal *internal) +{ + if (entry->type != EFREET_MENU_ENTRY_MENU) return 1; + if (!entry->name || !internal->name.name) return 1; + if (entry->name == internal->name.name) return 0; + return strcmp(entry->name, internal->name.name); +} + +static int +efreet_menu_cb_entry_compare_desktop(Efreet_Menu *entry, Efreet_Desktop *desktop) +{ + if (entry->type != EFREET_MENU_ENTRY_DESKTOP) return -1; + if (!entry->name || !desktop->name) return -1; + if (entry->name == desktop->name) return 0; + return strcmp(entry->name, desktop->name); +} + +static int +efreet_menu_cb_move_compare(Efreet_Menu_Move *move, const char *old) +{ + if (!move->old_name || !old) return 1; + if (move->old_name == old) return 0; + return 1; +} + +static int +efreet_menu_layout_is_empty(Efreet_Menu *entry) +{ + Efreet_Menu *sub_entry; + Eina_List *l; + + if (!entry->entries) return 1; + + EINA_LIST_FOREACH(entry->entries, l, sub_entry) + { + if (sub_entry->type == EFREET_MENU_ENTRY_MENU) return 0; + if (sub_entry->type == EFREET_MENU_ENTRY_DESKTOP) return 0; + } + return 1; +} diff --git a/src/lib/efreet_menu.h b/src/lib/efreet_menu.h new file mode 100644 index 0000000..8531cd8 --- /dev/null +++ b/src/lib/efreet_menu.h @@ -0,0 +1,138 @@ +#ifndef EFREET_MENU_H +#define EFREET_MENU_H + +/** + * @file efreet_menu.h + * @brief Contains the structures and methods to support the Desktop + * Menu Specification. + * @addtogroup Efreet_Menu Efreet_Menu: The FDO Desktop Menu Specification + * functions and structures + * + * @{ + */ + +/** + * The type of entry + */ +typedef enum Efreet_Menu_Entry_Type +{ + EFREET_MENU_ENTRY_MENU, + EFREET_MENU_ENTRY_DESKTOP, + EFREET_MENU_ENTRY_SEPARATOR, + EFREET_MENU_ENTRY_HEADER +} Efreet_Menu_Entry_Type; + +/** + * Efreet_Menu + */ +typedef struct Efreet_Menu Efreet_Menu; + +/** + * Efreet_Menu + * Stores information on a entry in the menu + */ +struct Efreet_Menu +{ + Efreet_Menu_Entry_Type type; + const char *id; /**< File-id for desktop and relative name for menu */ + + const char *name; /**< Name this entry should show */ + const char *icon; /**< Icon for this entry */ + + Efreet_Desktop *desktop; /**< The desktop we refer too */ + Eina_List *entries; /**< The menu items */ +}; + + +/** + * @return Returns no value + * @brief Initialize legacy kde support. This function blocks while + * the kde-config script is run. + */ +EAPI int efreet_menu_kde_legacy_init(void); + +/** + * @param name The internal name of the menu + * @return Returns the Efreet_Menu on success or + * NULL on failure + * @brief Creates a new menu + */ +EAPI Efreet_Menu *efreet_menu_new(const char *name); + +/** + * @brief Override which file is used for menu creation + * @param file The file to use for menu creation + * + * This file is only used if it exists, else the standard files will be used + * for the menu. + */ +EAPI void efreet_menu_file_set(const char *file); + +/** + * @return Returns the Efreet_Menu_Internal representation of the default menu or + * NULL if none found + * @brief Creates the default menu representation + */ +EAPI Efreet_Menu *efreet_menu_get(void); + +/** + * @param path The path of the menu to load + * @return Returns the Efreet_Menu_Internal representation on success or NULL on + * failure + * @brief Parses the given .menu file and creates the menu representation + */ +EAPI Efreet_Menu *efreet_menu_parse(const char *path); + +/** + * @param menu The menu to work with + * @param path The path where the menu should be saved + * @return Returns 1 on success, 0 on failure + * @brief Saves the menu to file + */ +EAPI int efreet_menu_save(Efreet_Menu *menu, const char *path); + +/** + * @param menu The Efreet_Menu to free + * @return Returns no value + * @brief Frees the given structure + */ +EAPI void efreet_menu_free(Efreet_Menu *menu); + + +/** + * @param menu The menu to work with + * @param desktop The desktop to insert + * @param pos The position to place the new desktop + * @return Returns 1 on success, 0 on failure + * @brief Insert a desktop element in a menu structure. Only accepts desktop files + * in default directories. + */ +EAPI int efreet_menu_desktop_insert(Efreet_Menu *menu, + Efreet_Desktop *desktop, + int pos); + +/** + * @param menu The menu to work with + * @param desktop The desktop to remove + * @return Returns 1 on success, 0 on failure + * @brief Remove a desktop element in a menu structure. Only accepts desktop files + * in default directories. + */ +EAPI int efreet_menu_desktop_remove(Efreet_Menu *menu, + Efreet_Desktop *desktop); + + +/** + * @param menu The menu to work with + * @param menu The menu to work with + * @param indent The indent level to print the menu at + * @return Returns no value + * @brief Dumps the contents of the menu to the command line + */ +EAPI void efreet_menu_dump(Efreet_Menu *menu, const char *indent); + +/** + * @} + */ + +#endif diff --git a/src/lib/efreet_mime.c b/src/lib/efreet_mime.c new file mode 100644 index 0000000..9343d90 --- /dev/null +++ b/src/lib/efreet_mime.c @@ -0,0 +1,1622 @@ +#ifdef HAVE_CONFIG_H +# include +#endif + +#ifdef HAVE_ALLOCA_H +# include +#elif defined __GNUC__ +# define alloca __builtin_alloca +#elif defined _AIX +# define alloca __alloca +#elif defined _MSC_VER +# include +# define alloca _alloca +#else +# include +# ifdef __cplusplus +extern "C" +# endif +void *alloca (size_t); +#endif + +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +# include +#endif + +#ifdef HAVE_NETINET_IN_H +# include +#endif + +#ifdef HAVE_ARPA_INET_H +# include +#endif + +#include +#include + +/* define macros and variable for using the eina logging system */ +#define EFREET_MODULE_LOG_DOM _efreet_mime_log_dom +static int _efreet_mime_log_dom = -1; + +#include "Efreet.h" +#include "Efreet_Mime.h" +#include "efreet_private.h" + +static Eina_List *globs = NULL; /* contains Efreet_Mime_Glob structs */ +static Eina_List *magics = NULL; /* contains Efreet_Mime_Magic structs */ +static Eina_Hash *wild = NULL; /* contains *.ext and mime.types globs*/ +static Eina_Hash *monitors = NULL; /* contains file monitors */ +static Eina_Hash *mime_icons = NULL; /* contains cache with mime->icons */ +static Eina_Inlist *mime_icons_lru = NULL; +static unsigned int _efreet_mime_init_count = 0; + +static const char *_mime_inode_symlink = NULL; +static const char *_mime_inode_fifo = NULL; +static const char *_mime_inode_chardevice = NULL; +static const char *_mime_inode_blockdevice = NULL; +static const char *_mime_inode_socket = NULL; +static const char *_mime_inode_mountpoint = NULL; +static const char *_mime_inode_directory = NULL; +static const char *_mime_application_x_executable = NULL; +static const char *_mime_application_octet_stream = NULL; +static const char *_mime_text_plain = NULL; + +/** + * @internal + * @brief Holds whether we are big/little endian + * @note This is set during efreet_mime_init based on + * a runtime check. + */ +static enum +{ + EFREET_ENDIAN_BIG = 0, + EFREET_ENDIAN_LITTLE = 1 +} efreet_mime_endianess = EFREET_ENDIAN_BIG; + +/* + * Buffer sized used for magic checks. The default is good enough for the + * current set of magic rules. This setting is only here for the future. + */ +#define EFREET_MIME_MAGIC_BUFFER_SIZE 512 + +/* + * Minimum timeout in seconds between mime-icons cache flush. + */ +#define EFREET_MIME_ICONS_FLUSH_TIMEOUT 60 + +/* + * Timeout in seconds, when older mime-icons items are expired. + */ +#define EFREET_MIME_ICONS_EXPIRE_TIMEOUT 600 + +/* + * mime-icons maximum population. + */ +#define EFREET_MIME_ICONS_MAX_POPULATION 512 + +/* + * If defined, dump mime-icons statistics after flush. + */ +//#define EFREET_MIME_ICONS_DEBUG + +typedef struct Efreet_Mime_Glob Efreet_Mime_Glob; +struct Efreet_Mime_Glob +{ + const char *glob; + const char *mime; +}; + +typedef struct Efreet_Mime_Magic Efreet_Mime_Magic; +struct Efreet_Mime_Magic +{ + unsigned int priority; + const char *mime; + Eina_List *entries; +}; + +typedef struct Efreet_Mime_Magic_Entry Efreet_Mime_Magic_Entry; +struct Efreet_Mime_Magic_Entry +{ + unsigned int indent; + unsigned int offset; + unsigned int word_size; + unsigned int range_len; + unsigned short value_len; + char *mask; + char *value; +}; + +typedef struct Efreet_Mime_Icon_Entry_Head Efreet_Mime_Icon_Entry_Head; +struct Efreet_Mime_Icon_Entry_Head +{ + EINA_INLIST; /* node of mime_icons_lru */ + Eina_Inlist *list; + const char *mime; + double timestamp; +}; + +typedef struct Efreet_Mime_Icon_Entry Efreet_Mime_Icon_Entry; +struct Efreet_Mime_Icon_Entry +{ + EINA_INLIST; + const char *icon; + const char *theme; + unsigned int size; +}; + +static int efreet_mime_glob_remove(const char *glob); +static void efreet_mime_mime_types_load(const char *file); +static void efreet_mime_shared_mimeinfo_globs_load(const char *file); +static void efreet_mime_shared_mimeinfo_magic_load(const char *file); +static void efreet_mime_shared_mimeinfo_magic_parse(char *data, int size); +static const char *efreet_mime_magic_check_priority(const char *file, + unsigned int start, + unsigned int end); +static int efreet_mime_init_files(void); +static const char *efreet_mime_special_check(const char *file); +static const char *efreet_mime_fallback_check(const char *file); +static void efreet_mime_glob_free(void *data); +static void efreet_mime_magic_free(void *data); +static void efreet_mime_magic_entry_free(void *data); +static int efreet_mime_glob_match(const char *str, const char *glob); +static int efreet_mime_glob_case_match(char *str, const char *glob); +static int efreet_mime_endian_check(void); + +static void efreet_mime_monitor_add(const char *file); +static void efreet_mime_cb_update_file(void *data, + Ecore_File_Monitor *monitor, + Ecore_File_Event event, + const char *path); + +static void efreet_mime_icons_flush(double now); +static void efreet_mime_icon_entry_head_free(Efreet_Mime_Icon_Entry_Head *entry); +static void efreet_mime_icon_entry_add(const char *mime, + const char *icon, + const char *theme, + unsigned int size); +static const char *efreet_mime_icon_entry_find(const char *mime, + const char *theme, + unsigned int size); +static void efreet_mime_icons_debug(void); + +EAPI int +efreet_mime_init(void) +{ + if (++_efreet_mime_init_count != 1) + return _efreet_mime_init_count; + + if (!ecore_init()) + return --_efreet_mime_init_count; + + if (!ecore_file_init()) + goto shutdown_ecore; + + if (!efreet_init()) + goto shutdown_ecore_file; + + _efreet_mime_log_dom = eina_log_domain_register + ("efreet_mime", EFREET_DEFAULT_LOG_COLOR); + + if (_efreet_mime_log_dom < 0) + { + EINA_LOG_ERR("Efreet: Could not create a log domain for efreet_mime."); + goto shutdown_efreet; + } + + efreet_mime_endianess = efreet_mime_endian_check(); + + monitors = eina_hash_string_superfast_new(EINA_FREE_CB(ecore_file_monitor_del)); + + efreet_mime_type_cache_clear(); + + if (!efreet_mime_init_files()) + goto unregister_log_domain; + + return _efreet_mime_init_count; + +unregister_log_domain: + eina_log_domain_unregister(_efreet_mime_log_dom); + _efreet_mime_log_dom = -1; +shutdown_efreet: + efreet_shutdown(); +shutdown_ecore_file: + ecore_file_shutdown(); +shutdown_ecore: + ecore_shutdown(); + + return --_efreet_mime_init_count; +} + +EAPI int +efreet_mime_shutdown(void) +{ + if (--_efreet_mime_init_count != 0) + return _efreet_mime_init_count; + + efreet_mime_icons_debug(); + + IF_RELEASE(_mime_inode_symlink); + IF_RELEASE(_mime_inode_fifo); + IF_RELEASE(_mime_inode_chardevice); + IF_RELEASE(_mime_inode_blockdevice); + IF_RELEASE(_mime_inode_socket); + IF_RELEASE(_mime_inode_mountpoint); + IF_RELEASE(_mime_inode_directory); + IF_RELEASE(_mime_application_x_executable); + IF_RELEASE(_mime_application_octet_stream); + IF_RELEASE(_mime_text_plain); + + IF_FREE_LIST(globs, efreet_mime_glob_free); + IF_FREE_LIST(magics, efreet_mime_magic_free); + IF_FREE_HASH(monitors); + IF_FREE_HASH(wild); + IF_FREE_HASH(mime_icons); + eina_log_domain_unregister(_efreet_mime_log_dom); + _efreet_mime_log_dom = -1; + efreet_shutdown(); + ecore_file_shutdown(); + ecore_shutdown(); + + return _efreet_mime_init_count; +} + +EAPI const char * +efreet_mime_type_get(const char *file) +{ + const char *type = NULL; + + EINA_SAFETY_ON_NULL_RETURN_VAL(file, NULL); + + if ((type = efreet_mime_special_check(file))) + return type; + + /* Check magics with priority > 80 */ + if ((type = efreet_mime_magic_check_priority(file, 0, 80))) + return type; + + /* Check globs */ + if ((type = efreet_mime_globs_type_get(file))) + return type; + + /* Check rest of magics */ + if ((type = efreet_mime_magic_check_priority(file, 80, 0))) + return type; + + return efreet_mime_fallback_check(file); +} + +EAPI const char * +efreet_mime_type_icon_get(const char *mime, const char *theme, unsigned int size) +{ + const char *icon = NULL; + char *data; + Eina_List *icons = NULL; + const char *env = NULL; + char *p = NULL, *pp = NULL, *ppp = NULL; + char buf[PATH_MAX]; + const char *cache; + + EINA_SAFETY_ON_NULL_RETURN_VAL(mime, NULL); + EINA_SAFETY_ON_NULL_RETURN_VAL(theme, NULL); + + mime = eina_stringshare_add(mime); + theme = eina_stringshare_add(theme); + cache = efreet_mime_icon_entry_find(mime, theme, size); + if (cache) + { + eina_stringshare_del(mime); + eina_stringshare_del(theme); + return cache; + } + + /* Standard icon name */ + p = strdup(mime); + pp = p; + while (*pp) + { + if (*pp == '/') *pp = '-'; + pp++; + } + icons = eina_list_append(icons, p); + + /* Environment Based icon names */ + if ((env = efreet_desktop_environment_get())) + { + snprintf(buf, sizeof(buf), "%s-mime-%s", env, p); + icons = eina_list_append(icons, strdup(buf)); + + snprintf(buf, sizeof(buf), "%s-%s", env, p); + icons = eina_list_append(icons, strdup(buf)); + } + + /* Mime prefixed icon names */ + snprintf(buf, sizeof(buf), "mime-%s", p); + icons = eina_list_append(icons, strdup(buf)); + + /* Generic icons */ + pp = strdup(p); + while ((ppp = strrchr(pp, '-'))) + { + *ppp = '\0'; + + snprintf(buf, sizeof(buf), "%s-x-generic", pp); + icons = eina_list_append(icons, strdup(buf)); + + snprintf(buf, sizeof(buf), "%s-generic", pp); + icons = eina_list_append(icons, strdup(buf)); + + snprintf(buf, sizeof(buf), "%s", pp); + icons = eina_list_append(icons, strdup(buf)); + } + FREE(pp); + + /* Search for icons using list */ + icon = efreet_icon_list_find(theme, icons, size); + while (icons) + { + data = eina_list_data_get(icons); + free(data); + icons = eina_list_remove_list(icons, icons); + } + + efreet_mime_icon_entry_add(mime, eina_stringshare_add(icon), theme, size); + + return icon; +} + +EAPI void +efreet_mime_type_cache_clear(void) +{ + if (mime_icons) + { + eina_hash_free(mime_icons); + mime_icons_lru = NULL; + } + mime_icons = eina_hash_stringshared_new(EINA_FREE_CB(efreet_mime_icon_entry_head_free)); +} + +EAPI void +efreet_mime_type_cache_flush(void) +{ + efreet_mime_icons_flush(ecore_loop_time_get()); +} + + +EAPI const char * +efreet_mime_magic_type_get(const char *file) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(file, NULL); + return efreet_mime_magic_check_priority(file, 0, 0); +} + +EAPI const char * +efreet_mime_globs_type_get(const char *file) +{ + Eina_List *l; + Efreet_Mime_Glob *g; + char *sl, *p; + const char *s; + char *ext, *mime; + + EINA_SAFETY_ON_NULL_RETURN_VAL(file, NULL); + + /* Check in the extension hash for the type */ + ext = strchr(file, '.'); + if (ext) + { + sl = alloca(strlen(ext) + 1); + for (s = ext, p = sl; *s; s++, p++) *p = tolower(*s); + *p = 0; + p = sl; + while (p) + { + p++; + if (p && (mime = eina_hash_find(wild, p))) return mime; + p = strchr(p, '.'); + } + } + + /* Fallback to the other globs if not found */ + EINA_LIST_FOREACH(globs, l, g) + { + if (efreet_mime_glob_match(file, g->glob)) + return g->mime; + } + + ext = alloca(strlen(file) + 1); + for (s = file, p = ext; *s; s++, p++) *p = tolower(*s); + *p = 0; + EINA_LIST_FOREACH(globs, l, g) + { + if (efreet_mime_glob_case_match(ext, g->glob)) + return g->mime; + } + return NULL; +} + +EAPI const char * +efreet_mime_special_type_get(const char *file) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(file, NULL); + return efreet_mime_special_check(file); +} + +EAPI const char * +efreet_mime_fallback_type_get(const char *file) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(file, NULL); + return efreet_mime_fallback_check(file); +} + +/** + * @internal + * @return Returns the endianess + * @brief Retreive the endianess of the machine + */ +static int +efreet_mime_endian_check(void) +{ + int test = 1; + return (*((char*)(&test))); +} + +/** + * @internal + * @param file File to monitor + * @return Returns no value. + * @brief Creates a new file monitor if we aren't already monitoring the + * given file + */ +static void +efreet_mime_monitor_add(const char *file) +{ + Ecore_File_Monitor *fm = NULL; + + /* if this is already in our hash then we're already monitoring so no + * reason to re-monitor */ + if (eina_hash_find(monitors, file)) + return; + + if ((fm = ecore_file_monitor_add(file, efreet_mime_cb_update_file, NULL))) + { + eina_hash_del(monitors, file, NULL); + eina_hash_add(monitors, file, fm); + } +} + +/** + * @internal + * @param datadirs List of XDG data dirs + * @param datahome Path to XDG data home directory + * @return Returns no value + * @brief Read all glob files in XDG data/home dirs. + * Also reads the /etc/mime.types file. + */ +static void +efreet_mime_load_globs(Eina_List *datadirs, const char *datahome) +{ + Eina_List *l; + char buf[4096]; + const char *datadir = NULL; + + IF_FREE_HASH(wild); + wild = eina_hash_string_superfast_new(EINA_FREE_CB(eina_stringshare_del)); + while (globs) + { + efreet_mime_glob_free(eina_list_data_get(globs)); + globs = eina_list_remove_list(globs, globs); + } + + /* + * This is here for legacy reasons. It is mentioned briefly + * in the spec and seems to still be quite valid. It is + * loaded first so the globs files will override anything + * in here. + */ + efreet_mime_mime_types_load("/etc/mime.types"); + + datadir = datahome; + snprintf(buf, sizeof(buf), "%s/mime/globs", datadir); + efreet_mime_shared_mimeinfo_globs_load(buf); + + EINA_LIST_FOREACH(datadirs, l, datadir) + { + snprintf(buf, sizeof(buf), "%s/mime/globs", datadir); + efreet_mime_shared_mimeinfo_globs_load(buf); + } +} + +/** + * @internal + * @param datadirs List of XDG data dirs + * @param datahome Path to XDG data home directory + * @return Returns no value + * @brief Read all magic files in XDG data/home dirs. + */ +static void +efreet_mime_load_magics(Eina_List *datadirs, const char *datahome) +{ + Eina_List *l; + char buf[4096]; + const char *datadir = NULL; + + while (magics) + { + efreet_mime_magic_free(eina_list_data_get(magics)); + magics = eina_list_remove_list(magics, magics); + } + + datadir = datahome; + snprintf(buf, sizeof(buf), "%s/mime/magic", datadir); + efreet_mime_shared_mimeinfo_magic_load(buf); + + EINA_LIST_FOREACH(datadirs, l, datadir) + { + snprintf(buf, sizeof(buf), "%s/mime/magic", datadir); + efreet_mime_shared_mimeinfo_magic_load(buf); + } +} + +/** + * @internal + * @param data Data pointer passed to monitor_add + * @param monitor Ecore_File_Monitor associated with this event + * @param event The type of event + * @param path Path to the file that was updated + * @return Returns no value + * @brief Callback for all file monitors. Just reloads the appropriate + * list depending on which file changed. If it was a magic file + * only the magic list is updated. If it was a glob file or /etc/mime.types, + * the globs are updated. + */ +static void +efreet_mime_cb_update_file(void *data __UNUSED__, + Ecore_File_Monitor *monitor __UNUSED__, + Ecore_File_Event event __UNUSED__, + const char *path) +{ + Eina_List *datadirs = NULL; + const char *datahome = NULL; + + if (!(datahome = efreet_data_home_get())) + return; + + if (!(datadirs = efreet_data_dirs_get())) + return; + + if (strstr(path, "magic")) + efreet_mime_load_magics(datadirs, datahome); + else + efreet_mime_load_globs(datadirs, datahome); +} + +/** + * @internal + * @param datadirs List of XDG data dirs + * @param datahome Path to XDG data home directory + * @return Returns 1 on success, 0 on failure + * @brief Initializes globs, magics, and monitors lists. + */ +static int +efreet_mime_init_files(void) +{ + Eina_List *l; + Eina_List *datadirs = NULL; + char buf[PATH_MAX]; + const char *datahome, *datadir = NULL; + + if (!(datahome = efreet_data_home_get())) + return 0; + + if (!(datadirs = efreet_data_dirs_get())) + return 0; + + /* + * Add our file monitors + * We watch the directories so we can watch for new files + */ + datadir = datahome; + snprintf(buf, sizeof(buf), "%s/mime", datadir); + efreet_mime_monitor_add(buf); + + EINA_LIST_FOREACH(datadirs, l, datadir) + { + snprintf(buf, sizeof(buf), "%s/mime", datadir); + efreet_mime_monitor_add(buf); + } + efreet_mime_monitor_add("/etc/mime.types"); + + /* Load our mime information */ + efreet_mime_load_globs(datadirs, datahome); + efreet_mime_load_magics(datadirs, datahome); + + _mime_inode_symlink = eina_stringshare_add("inode/symlink"); + _mime_inode_fifo = eina_stringshare_add("inode/fifo"); + _mime_inode_chardevice = eina_stringshare_add("inode/chardevice"); + _mime_inode_blockdevice = eina_stringshare_add("inode/blockdevice"); + _mime_inode_socket = eina_stringshare_add("inode/socket"); + _mime_inode_mountpoint = eina_stringshare_add("inode/mountpoint"); + _mime_inode_directory = eina_stringshare_add("inode/directory"); + _mime_application_x_executable = eina_stringshare_add("application/x-executable"); + _mime_application_octet_stream = eina_stringshare_add("application/octet-stream"); + _mime_text_plain = eina_stringshare_add("text/plain"); + + return 1; +} + +/** + * @internal + * @param file File to examine + * @return Returns mime type if special file, else NULL + * @brief Returns a mime type based on the stat of a file. + * This is used first to catch directories and other special + * files. A NULL return doesn't necessarily mean failure, but + * can also mean the file is regular. + * @note Mapping of file types to mime types: + * Stat Macro File Type Mime Type + * S_IFREG regular NULL + * S_IFIFO named pipe (fifo) inode/fifo + * S_IFCHR character special inode/chardevice + * S_IFDIR directory inode/directory + * S_IFBLK block special inode/blockdevice + * S_IFLNK symbolic link inode/symlink + * S_IFSOCK socket inode/socket + * + * This function can also return inode/mount-point. + * This is calculated by comparing the st_dev of the directory + * against that of it's parent directory. If they differ it + * is considered a mount point. + */ +static const char * +efreet_mime_special_check(const char *file) +{ + struct stat s; + int path_len = 0; + + /* no link on Windows < Vista */ +#ifdef _WIN32 + if (!stat(file, &s)) +#else + if (!lstat(file, &s)) +#endif + { + if (S_ISREG(s.st_mode)) + return NULL; + +#ifndef _WIN32 + if (S_ISLNK(s.st_mode)) + return _mime_inode_symlink; +#endif + + if (S_ISFIFO(s.st_mode)) + return _mime_inode_fifo; + + if (S_ISCHR(s.st_mode)) + return _mime_inode_chardevice; + + if (S_ISBLK(s.st_mode)) + return _mime_inode_blockdevice; + +#ifndef _WIN32 + if (S_ISSOCK(s.st_mode)) + return _mime_inode_socket; +#endif + + if (S_ISDIR(s.st_mode)) + { + struct stat s2; + char parent[PATH_MAX]; + char path[PATH_MAX]; + + strncpy(path, file, PATH_MAX); + + path_len = strlen(file); + strncpy(parent, path, PATH_MAX); + + /* Kill any trailing slash */ + parent[--path_len] = '\0'; + + /* Truncate to last slash */ + while (parent[--path_len] != '/') parent[path_len] = '\0'; + +#ifdef _WIN32 + if (!stat(file, &s2)) +#else + if (!lstat(parent, &s2)) +#endif + { + if (s.st_dev != s2.st_dev) + return _mime_inode_mountpoint; + } + + return _mime_inode_directory; + } + + return NULL; + } + + return NULL; +} + +/** + * @internal + * @param file File to examine + * @return Returns mime type or NULL if the file doesn't exist + * @brief Returns text/plain if the file appears to contain text and + * returns application/octet-stream if it appears to be binary. + */ +static const char * +efreet_mime_fallback_check(const char *file) +{ + FILE *f = NULL; + char buf[32]; + int i; + + if (ecore_file_can_exec(file)) + return _mime_application_x_executable; + + if (!(f = fopen(file, "r"))) return NULL; + + i = fread(buf, 1, sizeof(buf), f); + fclose(f); + + if (i == 0) return _mime_application_octet_stream; + + /* + * Check for ASCII control characters in the first 32 bytes. + * Line Feeds, carriage returns, and tabs are ignored as they are + * quite common in text files in the first 32 chars. + */ + for (i -= 1; i >= 0; --i) + { + if ((buf[i] < 0x20) && + (buf[i] != '\n') && /* Line Feed */ + (buf[i] != '\r') && /* Carriage Return */ + (buf[i] != '\t')) /* Tab */ + return _mime_application_octet_stream; + } + + return _mime_text_plain; +} + +/** + * @internal + * @param glob Glob to search for + * @return Returns 1 on success, 0 on failure + * @brief Removes a glob from the list + */ +static int +efreet_mime_glob_remove(const char *glob) +{ + Efreet_Mime_Glob *mime = NULL; + + if ((mime = eina_list_search_unsorted(globs, EINA_COMPARE_CB(strcmp), glob))) + { + globs = eina_list_remove(globs, mime); + IF_RELEASE(mime->glob); + IF_RELEASE(mime->mime); + FREE(mime); + return 1; + } + + return 0; +} + +static inline const char * +efreet_eat_space(const char *head, const Eina_File_Line *ln, Eina_Bool not) +{ + if (not) + { + while (!isspace(*head) && (head < ln->end)) + head++; + } + else + { + while (isspace(*head) && (head < ln->end)) + head++; + } + + return head; +} + +/** + * @internal + * @param file mime.types file to load + * @return Returns no value + * @brief Loads values from a mime.types style file + * into the globs list. + * @note Format: + * application/msaccess mdb + * application/msword doc dot + */ +static void +efreet_mime_mime_types_load(const char *file) +{ + const Eina_File_Line *ln; + Eina_Iterator *it; + Eina_File *f; + const char *head_line; + const char *word_start; + const char *mimetype; + + f = eina_file_open(file, 0); + if (!f) return ; + + it = eina_file_map_lines(f); + if (it) + { + Eina_Strbuf *ext; + + ext = eina_strbuf_new(); + + EINA_ITERATOR_FOREACH(it, ln) + { + head_line = efreet_eat_space(ln->start, ln, EINA_FALSE); + if (head_line == ln->end) continue ; + + if (*head_line == '#') continue ; + + word_start = head_line; + head_line = efreet_eat_space(head_line, ln, EINA_TRUE); + + if (head_line == ln->end) continue ; + mimetype = eina_stringshare_add_length(word_start, head_line - word_start); + do + { + head_line = efreet_eat_space(head_line, ln, EINA_FALSE); + if (head_line == ln->end) break ; + + word_start = head_line; + head_line = efreet_eat_space(head_line, ln, EINA_TRUE); + + eina_strbuf_append_length(ext, word_start, head_line - word_start); + + eina_hash_del(wild, + eina_strbuf_string_get(ext), + NULL); + eina_hash_add(wild, + eina_strbuf_string_get(ext), + eina_stringshare_ref(mimetype)); + + eina_strbuf_reset(ext); + } + while (head_line < ln->end); + + eina_stringshare_del(mimetype); + } + + eina_strbuf_free(ext); + eina_iterator_free(it); + } + eina_file_close(f); +} + +/** + * @internal + * @param file globs file to load + * @return Returns no value + * @brief Loads values from a mime.types style file + * into the globs list. + * @note Format: + * text/vnd.wap.wml:*.wml + * application/x-7z-compressed:*.7z + * application/vnd.corel-draw:*.cdr + * text/spreadsheet:*.sylk + */ +static void +efreet_mime_shared_mimeinfo_globs_load(const char *file) +{ + FILE *f = NULL; + char buf[4096], mimetype[4096], ext[4096], *p, *pp; + Efreet_Mime_Glob *mime = NULL; + + f = fopen(file, "rb"); + if (!f) return; + + while (fgets(buf, sizeof(buf), f)) + { + p = buf; + while (isspace(*p) && (*p != 0) && (*p != '\n')) p++; + + if (*p == '#') continue; + if ((*p == '\n') || (*p == 0)) continue; + + pp = p; + while ((*p != ':') && (*p != 0) && (*p != '\n')) p++; + + if ((*p == '\n') || (*p == 0)) continue; + strncpy(mimetype, pp, (p - pp)); + mimetype[p - pp] = 0; + p++; + pp = ext; + + while ((*p != 0) && (*p != '\n')) + { + *pp = *p; + pp++; + p++; + } + + *pp = 0; + + if (ext[0] == '*' && ext[1] == '.') + { + eina_hash_del(wild, &(ext[2]), NULL); + eina_hash_add(wild, &(ext[2]), + (void*)eina_stringshare_add(mimetype)); + } + else + { + mime = NEW(Efreet_Mime_Glob, 1); + if (mime) + { + mime->mime = eina_stringshare_add(mimetype); + mime->glob = eina_stringshare_add(ext); + if ((!mime->mime) || (!mime->glob)) + { + IF_RELEASE(mime->mime); + IF_RELEASE(mime->glob); + FREE(mime); + } + else + { + efreet_mime_glob_remove(ext); + globs = eina_list_append(globs, mime); + } + } + } + } + + fclose(f); +} + +/** + * @internal + * @param in Number to count the digits + * @return Returns number of digits + * @brief Calculates and returns the number of digits + * in a number. + */ +static int +efreet_mime_count_digits(int in) +{ + int i = 1, j = in; + + if (j < 10) return 1; + while ((j /= 10) > 0) ++i; + + return i; +} + +/** + * @internal + * @param file File to parse + * @return Returns no value + * @brief Loads a magic file and adds information to magics list + */ +static void +efreet_mime_shared_mimeinfo_magic_load(const char *file) +{ + int fd = -1, size; + char *data = (void *)-1; + + if (!file) return; + + size = ecore_file_size(file); + if (size <= 0) return; + + fd = open(file, O_RDONLY); + if (fd == -1) return; + + /* let's make mmap safe and just get 0 pages for IO erro */ + eina_mmap_safety_enabled_set(EINA_TRUE); + + data = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) + { + close(fd); + return; + } + + efreet_mime_shared_mimeinfo_magic_parse(data, size); + + munmap(data, size); + close(fd); +} + +/** + * @param data The data from the file + * @return Returns no value + * @brief Parses a magic file + * @note Format: + * + * ---------------------------------------------------------------------- + * | HEX | ASCII | + * ---------------------------------------------------------------------- + * |4D 49 4D 45 2D 4D 61 67 69 63 00 0A 5B 39 30 3A | MIME-Magic..[90: | + * |61 70 70 6C 69 63 61 74 69 6F 6E 2F 64 6F 63 62 | application/docb | + * |6F 6F 6B 2B 78 6D 6C 5D 0A 3E 30 3D 00 05 3C 3F | ook+xml].>0=..0=..-//OAS | + * |49 53 2F 2F 44 54 44 20 44 6F 63 42 6F 6F 6B 20 | IS//DTD DocBook | + * |58 4D 4C 2B 31 30 31 0A 31 3E 30 3D 00 17 2D 2F | XML+101.1>0=..-/ | + * ---------------------------------------------------------------------- + * + * indent + * The nesting depth of the rule, corresponding to the number of '>' + * characters in the traditional file format. + * ">" start-offset + * The offset into the file to look for a match. + * "=" value + * Two bytes giving the (big-endian) length of the value, followed by the + * value itself. + * "&" mask + * The mask, which (if present) is exactly the same length as the value. + * "~" word-size + * On little-endian machines, the size of each group to byte-swap. + * "+" range-length + * The length of the region in the file to check. + * + * The indent, range-length, word-size and mask components are optional. + * If missing, indent defaults to 0, range-length to 1, the word-size to 1, + * and the mask to all 'one' bits. In our case, mask is null as it is + * quicker, uses less memory and will achieve the same exact effect. + */ +static void +efreet_mime_shared_mimeinfo_magic_parse(char *data, int size) +{ + Efreet_Mime_Magic *mime = NULL; + Efreet_Mime_Magic_Entry *entry = NULL; + char *ptr; + + ptr = data; + + /* make sure we're a magic file */ + if (!ptr || (size < 12) || strncmp(ptr, "MIME-Magic\0\n", 12)) + return; + + ptr += 12; + + for (; (ptr - data) < size; ) + { + if (*ptr == '[') + { + char *val, buf[512]; + + mime = NEW(Efreet_Mime_Magic, 1); + magics = eina_list_append(magics, mime); + + val = ++ptr; + while ((*val != ':')) val++; + memcpy(&buf, ptr, val - ptr); + buf[val - ptr] = '\0'; + + mime->priority = atoi(buf); + ptr = ++val; + + while ((*val != ']')) val++; + memcpy(&buf, ptr, val - ptr); + buf[val - ptr] = '\0'; + + mime->mime = eina_stringshare_add(buf); + ptr = ++val; + + while (*ptr != '\n') ptr++; + ptr++; + } + else + { + short tshort; + + if (!mime) continue; + if (!entry) + { + if (!(entry = NEW(Efreet_Mime_Magic_Entry, 1))) + { + IF_FREE_LIST(magics, efreet_mime_magic_free); + return; + } + + entry->indent = 0; + entry->offset = 0; + entry->value_len = 0; + entry->word_size = 1; + entry->range_len = 1; + entry->mask = NULL; + entry->value = NULL; + + mime->entries = eina_list_append(mime->entries, entry); + } + + switch(*ptr) + { + case '>': + ptr ++; + entry->offset = atoi(ptr); + ptr += efreet_mime_count_digits(entry->offset); + break; + + case '=': + ptr++; + + tshort = 0; + memcpy(&tshort, ptr, sizeof(short)); + entry->value_len = ntohs(tshort); + ptr += 2; + + entry->value = NEW(1, entry->value_len); + memcpy(entry->value, ptr, entry->value_len); + ptr += entry->value_len; + break; + + case '&': + ptr++; + entry->mask = NEW(1, entry->value_len); + memcpy(entry->mask, ptr, entry->value_len); + ptr += entry->value_len; + break; + + case '~': + ptr++; + entry->word_size = atoi(ptr); + if ((entry->word_size != 0) && (((entry->word_size != 1) + && (entry->word_size != 2) + && (entry->word_size != 4)) + || (entry->value_len % entry->word_size))) + { + /* Invalid, Destroy */ + FREE(entry->value); + FREE(entry->mask); + FREE(entry); + + while (*ptr != '\n') ptr++; + break; + } + + if (efreet_mime_endianess == EFREET_ENDIAN_LITTLE) + { + int j; + + for (j = 0; j < entry->value_len; j += entry->word_size) + { + if (entry->word_size == 2) + { + ((short*)entry->value)[j] = + ntohs(((short*)entry->value)[j]); + + if (entry->mask) + ((short*)entry->mask)[j] = + ntohs(((short*)entry->mask)[j]); + } + else if (entry->word_size == 4) + { + ((int*)entry->value)[j] = + ntohl(((int*)entry->value)[j]); + + if (entry->mask) + ((int*)entry->mask)[j] = + ntohl(((int*)entry->mask)[j]); + } + } + } + + ptr += efreet_mime_count_digits(entry->word_size); + break; + + case '+': + ptr++; + entry->range_len = atoi(ptr); + ptr += efreet_mime_count_digits(entry->range_len); + break; + + case '\n': + ptr++; + entry = NULL; + break; + + default: + if (isdigit(*ptr)) + { + entry->indent = atoi(ptr); + ptr += efreet_mime_count_digits(entry->indent); + } + break; + } + } + } +/* + if (entry) + { + IF_FREE(entry->value); + IF_FREE(entry->mask); + FREE(entry); + } + */ +} + +/** + * @internal + * @param file File to check + * @param start Start priority, if 0 start at beginning + * @param end End priority, should be less then start + * unless start + * @return Returns mime type for file if found, NULL if not + * @brief Applies magic rules to a file given a start and end priority + */ +static const char * +efreet_mime_magic_check_priority(const char *file, + unsigned int start, + unsigned int end) +{ + Efreet_Mime_Magic *m = NULL; + Efreet_Mime_Magic_Entry *e = NULL; + Eina_List *l, *ll; + FILE *f = NULL; + unsigned int i = 0, offset = 0,level = 0, match = 0, bytes_read = 0; + const char *last_mime = NULL; + char c, v, buf[EFREET_MIME_MAGIC_BUFFER_SIZE]; + + f = fopen(file, "rb"); + if (!f) return NULL; + + if (!magics) + { + fclose(f); + return NULL; + } + + if ((bytes_read = fread(buf, 1, sizeof(buf), f)) == 0) + { + fclose(f); + return NULL; + } + + EINA_LIST_FOREACH(magics, l, m) + { + if ((start != 0) && (m->priority > start)) + continue; + + if (m->priority < end) + break; + + EINA_LIST_FOREACH(m->entries, ll, e) + { + if ((level < e->indent) && !match) + continue; + + if ((level >= e->indent) && !match) + level = e->indent; + + else if ((level > e->indent) && match) + { + fclose(f); + return last_mime; + } + + for (offset = e->offset; offset < e->offset + e->range_len; offset++) + { + if (((offset + e->value_len) > bytes_read) && + (fseek(f, offset, SEEK_SET) == -1)) + break; + + match = 1; + for (i = 0; i < e->value_len; ++i) + { + if (offset + e->value_len > bytes_read) + c = fgetc(f); + else + c = buf[offset + i]; + + v = e->value[i]; + if (e->mask) v &= e->mask[i]; + + if (!(c == v)) + { + match = 0; + break; + } + } + + if (match) + { + level += 1; + last_mime = m->mime; + break; + } + } + } + + if (match) + { + fclose(f); + return last_mime; + } + } + fclose(f); + + return NULL; +} + +/** + * @internal + * @param data Data pointer that is being destroyed + * @return Returns no value + * @brief Callback for globs destroy + */ +static void +efreet_mime_glob_free(void *data) +{ + Efreet_Mime_Glob *m = data; + + IF_RELEASE(m->mime); + IF_RELEASE(m->glob); + IF_FREE(m); +} + +/** + * @internal + * @param data Data pointer that is being destroyed + * @return Returns no value + * @brief Callback for magics destroy + */ +static void +efreet_mime_magic_free(void *data) +{ + Efreet_Mime_Magic *m = data; + + IF_RELEASE(m->mime); + IF_FREE_LIST(m->entries, efreet_mime_magic_entry_free); + IF_FREE(m); +} + +/** + * @internal + * @param data Data pointer that is being destroyed + * @return Returns no value + * @brief Callback for magic entry destroy + */ +static void +efreet_mime_magic_entry_free(void *data) +{ + Efreet_Mime_Magic_Entry *e = data; + + IF_FREE(e->mask); + IF_FREE(e->value); + IF_FREE(e); +} + + +/** + * @internal + * @param str String (filename) to match + * @param glob Glob to match str to + * @return Returns 1 on success, 0 on failure + * @brief Compares str to glob, case sensitive + */ +static int +efreet_mime_glob_match(const char *str, const char *glob) +{ + if (!str || !glob) return 0; + if (glob[0] == 0) + { + if (str[0] == 0) return 1; + return 0; + } + if (!fnmatch(glob, str, 0)) return 1; + return 0; +} + +/** + * @internal + * @param str String (filename) to match + * @param glob Glob to match str to + * @return Returns 1 on success, 0 on failure + * @brief Compares str to glob, case insensitive (expects str already in lower case) + */ +static int +efreet_mime_glob_case_match(char *str, const char *glob) +{ + const char *p; + char *tglob, *tp; + + if (!str || !glob) return 0; + if (glob[0] == 0) + { + if (str[0] == 0) return 1; + return 0; + } + tglob = alloca(strlen(glob) + 1); + for (tp = tglob, p = glob; *p; p++, tp++) *tp = tolower(*p); + *tp = 0; + if (!fnmatch(str, tglob, 0)) return 1; + return 0; +} + +static void +efreet_mime_icons_flush(double now) +{ + Eina_Inlist *l; + static double old = 0; + int todo; + + if (now - old < EFREET_MIME_ICONS_FLUSH_TIMEOUT) + return; + old = now; + + todo = eina_hash_population(mime_icons) - EFREET_MIME_ICONS_MAX_POPULATION; + if (todo <= 0) + return; + + l = mime_icons_lru->last; /* mime_icons_lru is not NULL, since todo > 0 */ + for (; todo > 0; todo--) + { + Efreet_Mime_Icon_Entry_Head *entry = (Efreet_Mime_Icon_Entry_Head *)l; + Eina_Inlist *prev = l->prev; + + mime_icons_lru = eina_inlist_remove(mime_icons_lru, l); + eina_hash_del_by_key(mime_icons, entry->mime); + l = prev; + } + + efreet_mime_icons_debug(); +} + +static void +efreet_mime_icon_entry_free(Efreet_Mime_Icon_Entry *node) +{ + eina_stringshare_del(node->icon); + eina_stringshare_del(node->theme); + free(node); +} + +static void +efreet_mime_icon_entry_head_free(Efreet_Mime_Icon_Entry_Head *entry) +{ + while (entry->list) + { + Efreet_Mime_Icon_Entry *n = (Efreet_Mime_Icon_Entry *)entry->list; + entry->list = eina_inlist_remove(entry->list, entry->list); + efreet_mime_icon_entry_free(n); + } + + eina_stringshare_del(entry->mime); + free(entry); +} + +static Efreet_Mime_Icon_Entry * +efreet_mime_icon_entry_new(const char *icon, + const char *theme, + unsigned int size) +{ + Efreet_Mime_Icon_Entry *entry; + + entry = malloc(sizeof(*entry)); + if (!entry) + return NULL; + + entry->icon = icon; + entry->theme = theme; + entry->size = size; + + return entry; +} + +static void +efreet_mime_icon_entry_add(const char *mime, + const char *icon, + const char *theme, + unsigned int size) +{ + Efreet_Mime_Icon_Entry_Head *entry; + Efreet_Mime_Icon_Entry *n; + + n = efreet_mime_icon_entry_new(icon, theme, size); + if (!n) + return; + entry = eina_hash_find(mime_icons, mime); + + if (entry) + { + Eina_Inlist *l; + + l = EINA_INLIST_GET(n); + entry->list = eina_inlist_prepend(entry->list, l); + + l = EINA_INLIST_GET(entry); + mime_icons_lru = eina_inlist_promote(mime_icons_lru, l); + } + else + { + Eina_Inlist *l; + + entry = malloc(sizeof(*entry)); + if (!entry) + { + efreet_mime_icon_entry_free(n); + return; + } + + l = EINA_INLIST_GET(n); + entry->list = eina_inlist_prepend(NULL, l); + entry->mime = mime; + eina_hash_direct_add(mime_icons, mime, entry); + + l = EINA_INLIST_GET(entry); + mime_icons_lru = eina_inlist_prepend(mime_icons_lru, l); + } + + entry->timestamp = ecore_loop_time_get(); + efreet_mime_icons_flush(entry->timestamp); +} + +static const char * +efreet_mime_icon_entry_find(const char *mime, + const char *theme, + unsigned int size) +{ + Efreet_Mime_Icon_Entry_Head *entry; + Efreet_Mime_Icon_Entry *n; + + entry = eina_hash_find(mime_icons, mime); + if (!entry) + return NULL; + + EINA_INLIST_FOREACH(entry->list, n) + { + if ((n->theme == theme) && (n->size == size)) + { + Eina_Inlist *l; + + l = EINA_INLIST_GET(n); + if (entry->list != l) + entry->list = eina_inlist_promote(entry->list, l); + + l = EINA_INLIST_GET(entry); + if (mime_icons_lru != l) + mime_icons_lru = eina_inlist_promote(mime_icons_lru, l); + + entry->timestamp = ecore_loop_time_get(); + return n->icon; + } + } + + return NULL; +} + +#ifdef EFREET_MIME_ICONS_DEBUG +static void +efreet_mime_icons_debug(void) +{ + double now = ecore_loop_time_get(); + Efreet_Mime_Icon_Entry_Head *entry; + EINA_INLIST_FOREACH(mime_icons_lru, entry) + { + Efreet_Mime_Icon_Entry *n; + + if ((now > 0) && + (now - entry->timestamp >= EFREET_MIME_ICONS_EXPIRE_TIMEOUT)) + { + puts("*** FOLLOWING ENTRIES ARE AGED AND CAN BE EXPIRED ***"); + now = 0; + } + + DBG("mime-icon entry: '%s' last used: %s", + entry->mime, ctime(&entry->timestamp)); + + EINA_INLIST_FOREACH(entry->list, n) + DBG("\tsize: %3u theme: '%s' icon: '%s'", + n->theme, n->size, n->icon); + } +} +#else +static void +efreet_mime_icons_debug(void) +{ +} +#endif diff --git a/src/lib/efreet_private.h b/src/lib/efreet_private.h new file mode 100644 index 0000000..ac43c4a --- /dev/null +++ b/src/lib/efreet_private.h @@ -0,0 +1,227 @@ +#ifndef EFREET_PRIVATE_H +#define EFREET_PRIVATE_H + +#ifdef ENABLE_NLS +# include +# define _(str) dgettext(PACKAGE, str) +#else +# define _(str) (str) +#endif + +/** + * @file efreet_private.h + * @brief Contains methods and defines that are private to the Efreet + * implementaion + * @addtogroup Efreet_Private Efreet_Private: Private methods and defines + * + * @{ + */ + +/** + * @def NEW(x, c) + * Allocate and zero out c structures of type x + */ +#define NEW(x, c) calloc(c, sizeof(x)) + +/** + * @def FREE(x) + * Free x and set to NULL + */ +#define FREE(x) do { free(x); x = NULL; } while (0) + +/** + * @def IF_FREE(x) + * If x is set, free x and set to NULL + */ +#define IF_FREE(x) do { if (x) FREE(x); } while (0) + +/** + * @def IF_RELEASE(x) + * If x is set, eina_stringshare_del x and set to NULL + */ +#define IF_RELEASE(x) do { \ + if (x) { \ + const char *__tmp; __tmp = (x); (x) = NULL; eina_stringshare_del(__tmp); \ + } \ + (x) = NULL; \ +} while (0) + +/** + * @def IF_FREE_LIST(x) + * If x is a valid pointer destroy x and set to NULL + */ +#define IF_FREE_LIST(list, free_cb) do { \ + void *_data; \ + EINA_LIST_FREE(list, _data) \ + free_cb(_data); \ + list = NULL; \ +} while (0) + +/** + * @def IF_FREE_HASH(x) + * If x is a valid pointer destroy x and set to NULL + */ +#define IF_FREE_HASH(x) do { \ + if (x) { \ + Eina_Hash *__tmp; __tmp = (x); (x) = NULL; eina_hash_free(__tmp); \ + } \ + (x) = NULL; \ +} while (0) + +/** + * @def IF_FREE_HASH_CB(x, cb) + * If x is a valid pointer destroy x with cb and set to NULL + */ +#define IF_FREE_HASH_CB(x, cb) do { \ + if (x) { \ + Eina_Hash *__tmp; __tmp = (x); (x) = NULL; efreet_hash_free(__tmp, cb); \ + } \ + (x) = NULL; \ +} while (0) + +#ifdef EFREET_DEFAULT_LOG_COLOR +#undef EFREET_DEFAULT_LOG_COLOR +#endif +#define EFREET_DEFAULT_LOG_COLOR "\033[36m" + +#ifndef EFREET_MODULE_LOG_DOM +#error "Need to define a log domain" +#endif + +/** + * macros that are used all around the code for message processing + * four macros are defined ERR, WRN, DGB, INF. + * EFREET_MODULE_LOG_DOM should be defined individually for each module + */ +#ifdef ERR +#undef ERR +#endif +#define ERR(...) EINA_LOG_DOM_ERR(EFREET_MODULE_LOG_DOM, __VA_ARGS__) +#ifdef DBG +#undef DBG +#endif +#define DBG(...) EINA_LOG_DOM_DBG(EFREET_MODULE_LOG_DOM, __VA_ARGS__) +#ifdef INF +#undef INF +#endif +#define INF(...) EINA_LOG_DOM_INFO(EFREET_MODULE_LOG_DOM, __VA_ARGS__) +#ifdef WRN +#undef WRN +#endif +#define WRN(...) EINA_LOG_DOM_WARN(EFREET_MODULE_LOG_DOM, __VA_ARGS__) + +typedef struct _Efreet_Cache_Icon Efreet_Cache_Icon; +typedef struct _Efreet_Cache_Icon_Element Efreet_Cache_Icon_Element; +typedef struct _Efreet_Cache_Fallback_Icon Efreet_Cache_Fallback_Icon; + +struct _Efreet_Cache_Icon +{ + const char *theme; + + Efreet_Cache_Icon_Element **icons; + unsigned int icons_count; +}; + +struct _Efreet_Cache_Icon_Element +{ + const char **paths; /* possible paths for icon */ + unsigned int paths_count; + + unsigned short type; /* size type of icon */ + + unsigned short normal; /* The size for this icon */ + unsigned short min; /* The minimum size for this icon */ + unsigned short max; /* The maximum size for this icon */ +}; + +struct _Efreet_Cache_Fallback_Icon +{ + const char *theme; + const char **icons; + unsigned int icons_count; +}; + +typedef struct _Efreet_Cache_Version Efreet_Cache_Version; +struct _Efreet_Cache_Version +{ + unsigned char major; + unsigned char minor; +}; + +typedef struct _Efreet_Cache_Hash Efreet_Cache_Hash; +struct _Efreet_Cache_Hash +{ + Eina_Hash *hash; +}; + +typedef struct _Efreet_Cache_Array_String Efreet_Cache_Array_String; +struct _Efreet_Cache_Array_String +{ + const char **array; + unsigned int array_count; +}; + +int efreet_base_init(void); +void efreet_base_shutdown(void); + +int efreet_cache_init(void); +void efreet_cache_shutdown(void); + +int efreet_icon_init(void); +void efreet_icon_shutdown(void); + +int efreet_menu_init(void); +void efreet_menu_shutdown(void); +EAPI Eina_List *efreet_default_dirs_get(const char *user_dir, + Eina_List *system_dirs, + const char *suffix); + +int efreet_ini_init(void); +void efreet_ini_shutdown(void); + +int efreet_desktop_init(void); +void efreet_desktop_shutdown(void); + +int efreet_util_init(void); +int efreet_util_shutdown(void); + +const char *efreet_home_dir_get(void); +void efreet_dirs_reset(void); + +const char *efreet_lang_get(void); +const char *efreet_lang_country_get(void); +const char *efreet_lang_modifier_get(void); + +size_t efreet_array_cat(char *buffer, size_t size, const char *strs[]); + +void efreet_cache_desktop_update(void); +void efreet_cache_desktop_close(void); +void efreet_cache_icon_update(void); + +Efreet_Desktop *efreet_cache_desktop_find(const char *file); +void efreet_cache_desktop_free(Efreet_Desktop *desktop); +void efreet_cache_desktop_add(Efreet_Desktop *desktop); +Efreet_Cache_Array_String *efreet_cache_desktop_dirs(void); + +Efreet_Cache_Icon *efreet_cache_icon_find(Efreet_Icon_Theme *theme, const char *icon); +Efreet_Cache_Fallback_Icon *efreet_cache_icon_fallback_find(const char *icon); +Efreet_Icon_Theme *efreet_cache_icon_theme_find(const char *theme); +Eina_List *efreet_cache_icon_theme_list(void); + +Efreet_Cache_Hash *efreet_cache_util_hash_string(const char *key); +Efreet_Cache_Hash *efreet_cache_util_hash_array_string(const char *key); +Efreet_Cache_Array_String *efreet_cache_util_names(const char *key); + +EAPI void efreet_cache_array_string_free(Efreet_Cache_Array_String *array); + +EAPI void efreet_hash_free(Eina_Hash *hash, Eina_Free_Cb free_cb); +EAPI void efreet_setowner(const char *path); +EAPI void efreet_fsetowner(int fd); + +EAPI extern int efreet_cache_update; + +/** + * @} + */ + +#endif diff --git a/src/lib/efreet_trash.c b/src/lib/efreet_trash.c new file mode 100644 index 0000000..27cc2b1 --- /dev/null +++ b/src/lib/efreet_trash.c @@ -0,0 +1,288 @@ +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include + +#include + +/* define macros and variable for using the eina logging system */ +#define EFREET_MODULE_LOG_DOM _efreet_trash_log_dom +static int _efreet_trash_log_dom = -1; + +#include "Efreet.h" +#include "Efreet_Trash.h" +#include "efreet_private.h" + +static unsigned int _efreet_trash_init_count = 0; +static const char *efreet_trash_dir = NULL; + +#ifdef _WIN32 +# define getuid() GetCurrentProcessId() +#endif + +EAPI int +efreet_trash_init(void) +{ + if (++_efreet_trash_init_count != 1) + return _efreet_trash_init_count; + + if (!eina_init()) + return --_efreet_trash_init_count; + + _efreet_trash_log_dom = eina_log_domain_register + ("efreet_trash", EFREET_DEFAULT_LOG_COLOR); + if (_efreet_trash_log_dom < 0) + { + EINA_LOG_ERR("Efreet: Could not create a log domain for efreet_trash"); + eina_shutdown(); + return --_efreet_trash_init_count; + } + return _efreet_trash_init_count; +} + +EAPI int +efreet_trash_shutdown(void) +{ + if (--_efreet_trash_init_count != 0) + return _efreet_trash_init_count; + + IF_RELEASE(efreet_trash_dir); + eina_log_domain_unregister(_efreet_trash_log_dom); + _efreet_trash_log_dom = -1; + eina_shutdown(); + + return _efreet_trash_init_count; +} + +EAPI const char* +efreet_trash_dir_get(const char *file) +{ + char buf[PATH_MAX]; + struct stat s_dest; + struct stat s_src; + const char *trash_dir = NULL; + + if (file) + { + if (stat(efreet_data_home_get(), &s_dest) != 0) + return NULL; + + if (stat(file, &s_src) != 0) + return NULL; + } + + if (!file || s_src.st_dev == s_dest.st_dev) + { + if (efreet_trash_dir && ecore_file_exists(efreet_trash_dir)) + { + eina_stringshare_ref(efreet_trash_dir); + return efreet_trash_dir; + } + + snprintf(buf, sizeof(buf), "%s/Trash", efreet_data_home_get()); + if (!ecore_file_exists(buf) && !ecore_file_mkpath(buf)) + return NULL; + + IF_RELEASE(efreet_trash_dir); + efreet_trash_dir = eina_stringshare_add(buf); + trash_dir = eina_stringshare_ref(efreet_trash_dir); + } + else + { + char *dir; + char path[PATH_MAX]; + + strncpy(buf, file, PATH_MAX); + buf[PATH_MAX - 1] = 0; + path[0] = 0; + + while (strlen(buf) > 1) + { + strncpy(path, buf, PATH_MAX); + dir = dirname(buf); + + if (stat(dir, &s_dest) == 0) + { + if (s_src.st_dev == s_dest.st_dev){ + + strncpy(buf, dir, PATH_MAX); + continue; + } + else + { + /* other device */ + break; + } + } + path[0] = 0; + break; + } + + if (path[0]) + { + snprintf(buf, sizeof(buf), "%s/.Trash-%d", path, getuid()); + if (!ecore_file_exists(buf) && !ecore_file_mkpath(buf)) + return NULL; + + trash_dir = eina_stringshare_add(buf); + } + } + if (trash_dir) + { + snprintf(buf, sizeof(buf), "%s/files", trash_dir); + if (!ecore_file_exists(buf) && !ecore_file_mkpath(buf)) + { + eina_stringshare_del(trash_dir); + return NULL; + } + + snprintf(buf, sizeof(buf), "%s/info", trash_dir); + if (!ecore_file_exists(buf) && !ecore_file_mkpath(buf)) + { + eina_stringshare_del(trash_dir); + return NULL; + } + } + + return trash_dir; +} + +EAPI int +efreet_trash_delete_uri(Efreet_Uri *uri, int force_delete) +{ + char dest[PATH_MAX]; + char times[64]; + const char *fname; + const char *escaped; + const char *trash_dir; + int i = 1; + time_t now; + FILE *f; + + EINA_SAFETY_ON_NULL_RETURN_VAL(uri, 0); + EINA_SAFETY_ON_NULL_RETURN_VAL(uri->path, 0); + EINA_SAFETY_ON_FALSE_RETURN_VAL(ecore_file_can_write(uri->path), 0); + + fname = ecore_file_file_get(uri->path); + + trash_dir = efreet_trash_dir_get(uri->path); + if (!trash_dir) + { + ERR("EFREET TRASH ERROR: No trash directory."); + return 0; + } + snprintf(dest, sizeof(dest), "%s/files/%s", trash_dir, fname); + + /* search for a free filename */ + while (ecore_file_exists(dest) && (i < 100)) + snprintf(dest, sizeof(dest), "%s/files/%s$%d", + trash_dir, fname, i++); + + fname = ecore_file_file_get(dest); + + /* move file to trash dir */ + if (rename(uri->path, dest)) + { + if (errno == EXDEV) + { + if (!force_delete) + { + eina_stringshare_del(trash_dir); + return -1; + } + + if (!ecore_file_recursive_rm(uri->path)) + { + ERR("EFREET TRASH ERROR: Can't delete file."); + eina_stringshare_del(trash_dir); + return 0; + } + } + else + { + ERR("EFREET TRASH ERROR: Can't move file to trash."); + eina_stringshare_del(trash_dir); + return 0; + } + } + + /* create info file */ + snprintf(dest, sizeof(dest), "%s/info/%s.trashinfo", trash_dir, fname); + + if ((f = fopen(dest, "w"))) + { + fputs("[Trash Info]\n", f); + + fputs("Path=", f); + escaped = efreet_uri_encode(uri); + fputs(escaped + 7, f); // +7 == don't write 'file://' + IF_RELEASE(escaped); + + time(&now); + strftime(times, sizeof(times), "%Y-%m-%dT%H:%M:%S", localtime(&now)); + fputs("\nDeletionDate=", f); + fputs(times, f); + fputs("\n", f); + fclose(f); + } + else + { + ERR("EFREET TRASH ERROR: Can't create trash info file."); + return 0; + } + + return 1; +} + +EAPI int +efreet_trash_is_empty(void) +{ + char buf[PATH_MAX]; + + snprintf(buf, sizeof(buf), "%s/files", efreet_trash_dir_get(NULL)); + + /* TODO Check also trash in other filesystems */ + return ecore_file_dir_is_empty(buf); +} + +EAPI int +efreet_trash_empty_trash(void) +{ + char buf[PATH_MAX]; + + snprintf(buf, sizeof(buf), "%s/info", efreet_trash_dir_get(NULL)); + if (!ecore_file_recursive_rm(buf)) return 0; + ecore_file_mkdir(buf); + + snprintf(buf, sizeof(buf), "%s/files", efreet_trash_dir_get(NULL)); + if (!ecore_file_recursive_rm(buf)) return 0; + ecore_file_mkdir(buf); + + /* TODO Empty also trash in other filesystems */ + return 1; +} + +EAPI Eina_List* +efreet_trash_ls(void) +{ + char *infofile; + char buf[PATH_MAX]; + Eina_List *files, *l; + + // NOTE THIS FUNCTION NOW IS NOT COMPLETE AS I DON'T NEED IT + // TODO read the name from the infofile instead of the filename + + snprintf(buf, sizeof(buf), "%s/files", efreet_trash_dir_get(NULL)); + files = ecore_file_ls(buf); + + if (eina_log_domain_level_check(_efreet_trash_log_dom, EINA_LOG_LEVEL_INFO)) + EINA_LIST_FOREACH(files, l, infofile) + INF("FILE: %s\n", infofile); + + return files; +} + diff --git a/src/lib/efreet_uri.c b/src/lib/efreet_uri.c new file mode 100644 index 0000000..20ebe39 --- /dev/null +++ b/src/lib/efreet_uri.c @@ -0,0 +1,119 @@ +#ifdef HAVE_CONFIG_H +# include +#endif + +#include + +#ifndef _POSIX_HOST_NAME_MAX +#define _POSIX_HOST_NAME_MAX 255 +#endif + +#ifdef HAVE_EVIL +# include +#endif + +/* define macros and variable for using the eina logging system */ +#define EFREET_MODULE_LOG_DOM /* no logging in this file */ + +#include "Efreet.h" +#include "efreet_private.h" + + +EAPI Efreet_Uri * +efreet_uri_decode(const char *full_uri) +{ + Efreet_Uri *uri; + const char *p; + char protocol[64], hostname[_POSIX_HOST_NAME_MAX], path[PATH_MAX]; + int i = 0; + + EINA_SAFETY_ON_NULL_RETURN_VAL(full_uri, NULL); + + /* An uri should be in the form :/// */ + if (!strstr(full_uri, "://")) return NULL; + + memset(protocol, 0, 64); + memset(hostname, 0, _POSIX_HOST_NAME_MAX); + memset(path, 0, PATH_MAX); + + /* parse protocol */ + p = full_uri; + for (i = 0; *p != ':' && *p != '\0' && i < 64; p++, i++) + protocol[i] = *p; + protocol[i] = '\0'; + + /* parse hostname */ + p += 3; + if (*p != '/') + { + for (i = 0; *p != '/' && *p != '\0' && i < _POSIX_HOST_NAME_MAX; p++, i++) + hostname[i] = *p; + hostname[i] = '\0'; + } + else + hostname[0] = '\0'; + + /* parse path */ + /* See http://www.faqs.org/rfcs/rfc1738.html for the escaped chars */ + for (i = 0; *p != '\0' && i < PATH_MAX; i++, p++) + { + if (*p == '%') + { + path[i] = *(++p); + path[i + 1] = *(++p); + path[i] = (char)strtol(&(path[i]), NULL, 16); + path[i + 1] = '\0'; + } + else + path[i] = *p; + } + + uri = NEW(Efreet_Uri, 1); + if (!uri) return NULL; + + uri->protocol = eina_stringshare_add(protocol); + uri->hostname = eina_stringshare_add(hostname); + uri->path = eina_stringshare_add(path); + + return uri; +} + +EAPI const char * +efreet_uri_encode(Efreet_Uri *uri) +{ + char dest[PATH_MAX * 3 + 4]; + const char *p; + int i; + + EINA_SAFETY_ON_NULL_RETURN_VAL(uri, NULL); + EINA_SAFETY_ON_NULL_RETURN_VAL(uri->path, NULL); + EINA_SAFETY_ON_NULL_RETURN_VAL(uri->protocol, NULL); + + memset(dest, 0, PATH_MAX * 3 + 4); + snprintf(dest, strlen(uri->protocol) + 4, "%s://", uri->protocol); + + /* Most app doesn't handle the hostname in the uri so it's put to NULL */ + for (i = strlen(uri->protocol) + 3, p = uri->path; *p != '\0'; p++, i++) + { + if (isalnum(*p) || strchr("/$-_.+!*'()", *p)) + dest[i] = *p; + else + { + snprintf(&(dest[i]), 4, "%%%02X", (unsigned char) *p); + i += 2; + } + } + + return eina_stringshare_add(dest); +} + +EAPI void +efreet_uri_free(Efreet_Uri *uri) +{ + if (!uri) return; + + IF_RELEASE(uri->protocol); + IF_RELEASE(uri->path); + IF_RELEASE(uri->hostname); + FREE(uri); +} diff --git a/src/lib/efreet_uri.h b/src/lib/efreet_uri.h new file mode 100644 index 0000000..32aaeee --- /dev/null +++ b/src/lib/efreet_uri.h @@ -0,0 +1,62 @@ +#ifndef EFREET_URI_H +#define EFREET_URI_H + +/** + * @file efreet_uri.h + * @brief Contains the methods used to support the FDO URI specification. + * @addtogroup Efreet_Uri Efreet_Uri: The FDO URI Specification functions + * @{ + */ + + +/** + * Efreet_Uri + */ +typedef struct Efreet_Uri Efreet_Uri; + +/** + * Efreet_Uri + * @brief Contains a simple rappresentation of an uri. The string don't have + * special chars escaped. + */ +struct Efreet_Uri +{ + const char *protocol; /**< The protocol used (usually 'file')*/ + const char *hostname; /**< The name of the host if any, or NULL */ + const char *path; /**< The full file path whitout protocol nor host*/ +}; + + + +/** + * @param uri Create an URI string from an Efreet_Uri struct + * @return The string rapresentation of uri (ex: 'file:///home/my%20name') + * @brief Get the string rapresentation of the given uri struct escaping + * illegal caracters. Remember to free the string with eina_stringshare_del() + * when you don't need it anymore. + * @note The resulting string will contain the protocol and the path but not + * the hostname, as many apps doesn't handle it. + */ +EAPI const char *efreet_uri_encode(Efreet_Uri *uri); + +/** + * @param val a valid uri string to parse + * @return Return The corresponding Efreet_Uri structure. Or NULL on errors. + * @brief Read a single uri and return an Efreet_Uri struct. If there's no + * hostname in the uri then the hostname parameter will be NULL. All the uri + * escaped chars will be converted to normal. + */ +EAPI Efreet_Uri *efreet_uri_decode(const char *val); + +/** + * @param uri The uri to free + * @brief Free the given uri structure. + */ +EAPI void efreet_uri_free(Efreet_Uri *uri); + + +/** + * @} + */ + +#endif diff --git a/src/lib/efreet_utils.c b/src/lib/efreet_utils.c new file mode 100644 index 0000000..dab85ef --- /dev/null +++ b/src/lib/efreet_utils.c @@ -0,0 +1,488 @@ +#ifdef HAVE_CONFIG_H +# include +#endif + +/* TODO: add no_display check, as we might want only displayable items */ + +#undef alloca +#ifdef HAVE_ALLOCA_H +# include +#elif defined __GNUC__ +# define alloca __builtin_alloca +#elif defined _AIX +# define alloca __alloca +#elif defined _MSC_VER +# include +# define alloca _alloca +#else +# include +# ifdef __cplusplus +extern "C" +# endif +void *alloca (size_t); +#endif + +#include + +#include + +/* define macros and variable for using the eina logging system */ +#define EFREET_MODULE_LOG_DOM _efreet_utils_log_dom +static int _efreet_utils_log_dom = -1; + +#include "Efreet.h" +#include "efreet_private.h" + +static char *efreet_util_path_in_default(const char *section, const char *path); + +static int efreet_util_glob_match(const char *str, const char *glob); + +static Eina_List *efreet_util_menus_find_helper(Eina_List *menus, const char *config_dir); + +static Efreet_Desktop *efreet_util_cache_find(const char *search, const char *what1, const char *what2); +static Eina_List *efreet_util_cache_list(const char *search, const char *what); +static Eina_List *efreet_util_cache_glob_list(const char *search, const char *what); + +static Eina_Hash *file_id_by_desktop_path = NULL; + +static int init = 0; + +int +efreet_util_init(void) +{ + if (init++) return init; + _efreet_utils_log_dom = eina_log_domain_register + ("efreet_util", EFREET_DEFAULT_LOG_COLOR); + if (_efreet_utils_log_dom < 0) + { + EINA_LOG_ERR("Efreet: Could not create a log domain for efreet_util"); + return 0; + } + + file_id_by_desktop_path = eina_hash_string_superfast_new(EINA_FREE_CB(eina_stringshare_del)); + + return init; +} + +int +efreet_util_shutdown(void) +{ + if (--init) return init; + + eina_log_domain_unregister(_efreet_utils_log_dom); + _efreet_utils_log_dom = -1; + IF_FREE_HASH(file_id_by_desktop_path); + + return init; +} + +static char * +efreet_util_path_in_default(const char *section, const char *path) +{ + Eina_List *dirs; + char *ret = NULL; + char *dir; + + dirs = efreet_default_dirs_get(efreet_data_home_get(), efreet_data_dirs_get(), + section); + + EINA_LIST_FREE(dirs, dir) + { + if (!strncmp(path, dir, strlen(dir))) + ret = dir; + else + eina_stringshare_del(dir); + } + + return ret; +} + +EAPI const char * +efreet_util_path_to_file_id(const char *path) +{ + size_t len, len2; + char *tmp, *p; + char *base; + const char *file_id; + + EINA_SAFETY_ON_NULL_RETURN_VAL(path, NULL); + + file_id = eina_hash_find(file_id_by_desktop_path, path); + if (file_id) return file_id; + + base = efreet_util_path_in_default("applications", path); + if (!base) return NULL; + + len = strlen(base); + if (strlen(path) <= len) + { + eina_stringshare_del(base); + return NULL; + } + if (strncmp(path, base, len)) + { + eina_stringshare_del(base); + return NULL; + } + + len2 = strlen(path + len + 1) + 1; + tmp = alloca(len2); + memcpy(tmp, path + len + 1, len2); + p = tmp; + while (*p) + { + if (*p == '/') *p = '-'; + p++; + } + eina_stringshare_del(base); + file_id = eina_stringshare_add(tmp); + eina_hash_add(file_id_by_desktop_path, path, (void *)file_id); + return file_id; +} + +EAPI Eina_List * +efreet_util_desktop_mime_list(const char *mime) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(mime, NULL); + return efreet_util_cache_list("mime_types", mime); +} + +EAPI Efreet_Desktop * +efreet_util_desktop_wm_class_find(const char *wmname, const char *wmclass) +{ + EINA_SAFETY_ON_TRUE_RETURN_VAL((!wmname) && (!wmclass), NULL); + return efreet_util_cache_find("startup_wm_class", wmname, wmclass); +} + +EAPI Efreet_Desktop * +efreet_util_desktop_file_id_find(const char *file_id) +{ + Efreet_Cache_Hash *hash; + Efreet_Desktop *ret = NULL; + const char *str; + + EINA_SAFETY_ON_NULL_RETURN_VAL(file_id, NULL); + + hash = efreet_cache_util_hash_string("file_id"); + if (!hash) return NULL; + str = eina_hash_find(hash->hash, file_id); + if (str) + ret = efreet_desktop_get(str); + return ret; +} + +EAPI Efreet_Desktop * +efreet_util_desktop_exec_find(const char *exec) +{ + Efreet_Cache_Hash *hash = NULL; + Efreet_Desktop *ret = NULL; + Efreet_Cache_Array_String *names = NULL; + unsigned int i; + + EINA_SAFETY_ON_NULL_RETURN_VAL(exec, NULL); + + names = efreet_cache_util_names("exec_list"); + if (!names) return NULL; + for (i = 0; i < names->array_count; i++) + { + const char *file; + char *exe; + unsigned int j; + Efreet_Cache_Array_String *array; + + exe = ecore_file_app_exe_get(names->array[i]); + if (!exe) continue; + file = ecore_file_file_get(exe); + if (!file) continue; + if (strcmp(exec, exe) && strcmp(exec, file)) + { + free(exe); + continue; + } + free(exe); + + if (!hash) + hash = efreet_cache_util_hash_array_string("exec_hash"); + if (!hash) return NULL; + array = eina_hash_find(hash->hash, names->array[i]); + if (!array) continue; + for (j = 0; j < array->array_count; j++) + { + ret = efreet_desktop_get(array->array[j]); + if (ret) break; + } + if (ret) break; + } + return ret; +} + +EAPI Efreet_Desktop * +efreet_util_desktop_name_find(const char *name) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(name, NULL); + return efreet_util_cache_find("name", name, NULL); +} + +EAPI Efreet_Desktop * +efreet_util_desktop_generic_name_find(const char *generic_name) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(generic_name, NULL); + return efreet_util_cache_find("generic_name", generic_name, NULL); +} + +EAPI Eina_List * +efreet_util_desktop_name_glob_list(const char *glob) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(glob, NULL); + return efreet_util_cache_glob_list("name", glob); +} + +EAPI Eina_List * +efreet_util_desktop_exec_glob_list(const char *glob) +{ + Efreet_Cache_Hash *hash = NULL; + Eina_List *ret = NULL; + Efreet_Cache_Array_String *names = NULL; + unsigned int i; + + EINA_SAFETY_ON_NULL_RETURN_VAL(glob, NULL); + + if (!strcmp(glob, "*")) + glob = NULL; + + names = efreet_cache_util_names("exec_list"); + if (!names) return NULL; + for (i = 0; i < names->array_count; i++) + { + Efreet_Cache_Array_String *array; + unsigned int j; + char *exe; + Efreet_Desktop *desk; + + exe = ecore_file_app_exe_get(names->array[i]); + if (!exe) continue; + if (glob && !efreet_util_glob_match(exe, glob)) + { + free(exe); + continue; + } + free(exe); + + if (!hash) + hash = efreet_cache_util_hash_array_string("exec_hash"); + if (!hash) return NULL; + + array = eina_hash_find(hash->hash, names->array[i]); + if (!array) continue; + for (j = 0; j < array->array_count; j++) + { + desk = efreet_desktop_get(array->array[j]); + if (desk) + ret = eina_list_append(ret, desk); + } + } + return ret; +} + +EAPI Eina_List * +efreet_util_desktop_generic_name_glob_list(const char *glob) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(glob, NULL); + return efreet_util_cache_glob_list("generic_name", glob); +} + +EAPI Eina_List * +efreet_util_desktop_comment_glob_list(const char *glob) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(glob, NULL); + return efreet_util_cache_glob_list("comment", glob); +} + +EAPI Eina_List * +efreet_util_desktop_categories_list(void) +{ + Efreet_Cache_Array_String *array; + Eina_List *ret = NULL; + unsigned int i; + + array = efreet_cache_util_names("categories_list"); + if (!array) return NULL; + for (i = 0; i < array->array_count; i++) + ret = eina_list_append(ret, array->array[i]); + return ret; +} + +EAPI Eina_List * +efreet_util_desktop_category_list(const char *category) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(category, NULL); + return efreet_util_cache_list("categories", category); +} + +static int +efreet_util_glob_match(const char *str, const char *glob) +{ + if (!str || !glob) + return 0; + if (glob[0] == '\0') + { + if (str[0] == '\0') return 1; + return 0; + } + if (!strcmp(glob, "*")) return 1; + if (!fnmatch(glob, str, 0)) return 1; + return 0; +} + +EAPI Eina_List * +efreet_util_menus_find(void) +{ + Eina_List *menus = NULL; + Eina_List *dirs, *l; + const char *dir; + + menus = efreet_util_menus_find_helper(menus, efreet_config_home_get()); + + dirs = efreet_config_dirs_get(); + EINA_LIST_FOREACH(dirs, l, dir) + menus = efreet_util_menus_find_helper(menus, dir); + + return menus; +} + +static Eina_List * +efreet_util_menus_find_helper(Eina_List *menus, const char *config_dir) +{ + Eina_Iterator *it; + Eina_File_Direct_Info *info; + char dbuf[PATH_MAX]; + + snprintf(dbuf, sizeof(dbuf), "%s/menus", config_dir); + it = eina_file_direct_ls(dbuf); + if (!it) return menus; + EINA_ITERATOR_FOREACH(it, info) + { + const char *exten; + exten = strrchr(info->path + info->name_start, '.'); + if (!exten) continue; + if (strcmp(".menu", exten)) continue; + + if (ecore_file_is_dir(info->path)) continue; + + menus = eina_list_append(menus, strdup(info->path)); + } + eina_iterator_free(it); + return menus; +} + +static Efreet_Desktop * +efreet_util_cache_find(const char *search, const char *what1, const char *what2) +{ + Efreet_Cache_Hash *hash; + Efreet_Desktop *ret = NULL; + Efreet_Cache_Array_String *array = NULL; + char key[256]; + + if ((!what1) && (!what2)) return NULL; + + snprintf(key, sizeof(key), "%s_hash", search); + hash = efreet_cache_util_hash_array_string(key); + if (!hash) return NULL; + if (what1) + array = eina_hash_find(hash->hash, what1); + if (!array && what2) array = eina_hash_find(hash->hash, what2); + if (array) + { + unsigned int i; + + for (i = 0; i < array->array_count; i++) + { + ret = efreet_desktop_get(array->array[i]); + if (ret) break; + } + } + return ret; +} + +static Eina_List * +efreet_util_cache_list(const char *search, const char *what) +{ + Efreet_Cache_Hash *hash; + Efreet_Cache_Array_String *array; + Eina_List *ret = NULL; + char key[256]; + + if (!what) return NULL; + + snprintf(key, sizeof(key), "%s_hash", search); + hash = efreet_cache_util_hash_array_string(key); + if (!hash) return NULL; + array = eina_hash_find(hash->hash, what); + if (array) + { + unsigned int i; + Efreet_Desktop *desk; + + for (i = 0; i < array->array_count; i++) + { + desk = efreet_desktop_get(array->array[i]); + if (desk) + ret = eina_list_append(ret, desk); + } + } + return ret; +} + +static Eina_List * +efreet_util_cache_glob_list(const char *search, const char *what) +{ + Efreet_Cache_Hash *hash = NULL; + Eina_List *ret = NULL; + Efreet_Cache_Array_String *names = NULL; + char key[256]; + unsigned int i; + + if (!what) return NULL; + if (!strcmp(what, "*")) + what = NULL; + + snprintf(key, sizeof(key), "%s_list", search); + names = efreet_cache_util_names(key); + if (!names) return NULL; + for (i = 0; i < names->array_count; i++) + { + Efreet_Cache_Array_String *array; + unsigned int j; + Efreet_Desktop *desk; + + if (what && !efreet_util_glob_match(names->array[i], what)) continue; + + if (!hash) + { + snprintf(key, sizeof(key), "%s_hash", search); + hash = efreet_cache_util_hash_array_string(key); + } + if (!hash) return NULL; + + array = eina_hash_find(hash->hash, names->array[i]); + if (!array) continue; + for (j = 0; j < array->array_count; j++) + { + desk = efreet_desktop_get(array->array[j]); + if (desk) + ret = eina_list_append(ret, desk); + } + } + return ret; +} + +/* + * Needs EAPI because of helper binaries + */ +EAPI void +efreet_hash_free(Eina_Hash *hash, Eina_Free_Cb free_cb) +{ + eina_hash_free_cb_set(hash, free_cb); + eina_hash_free(hash); +} + diff --git a/src/lib/efreet_utils.h b/src/lib/efreet_utils.h new file mode 100644 index 0000000..6a5e69e --- /dev/null +++ b/src/lib/efreet_utils.h @@ -0,0 +1,157 @@ +#ifndef EFREET_UTILS_H +#define EFREET_UTILS_H + +/** + * @file efreet_utils.h + * @brief Contains utility functions to ease usage of Efreet. + * FDO desktop entry specificiation. + * @addtogroup Efreet_Utils Efreet utilities for FDO + * + * @{ + */ + + +/** + * Returns the fdo file id for a given path. If the file isn't inside + * a default fdo path it will return NULL. + * + * @param path The path to find the file id for + * + * @return File id for path or NULL + */ +EAPI const char *efreet_util_path_to_file_id(const char *path); + + +/** + * Find all desktops for a given mime type + * + * This list must be freed using EINA_LIST_FREE / efreet_desktop_free + * + * @param mime the mime type + * @return a list of desktops + */ +EAPI Eina_List *efreet_util_desktop_mime_list(const char *mime); + + +/** + * Find all desktops for a given wm class + * + * This list must be freed using EINA_LIST_FREE / efreet_desktop_free + * + * @param wmname the wm name + * @param wmclass the wm class + * @return a list of desktops + */ +EAPI Efreet_Desktop *efreet_util_desktop_wm_class_find(const char *wmname, const char *wmclass); + +/** + * Find a desktop by file id + * + * return value must be freed by efreet_desktop_free + * + * @param file_id the file id + * @return a desktop + */ +EAPI Efreet_Desktop *efreet_util_desktop_file_id_find(const char *file_id); + +/** + * Find a desktop by exec + * + * return value must be freed by efreet_desktop_free + * + * @param exec the exec name + * @return a desktop + */ +EAPI Efreet_Desktop *efreet_util_desktop_exec_find(const char *exec); + +/** + * Find a desktop by name + * + * return value must be freed by efreet_desktop_free + * + * @param name the name + * @return a desktop + */ +EAPI Efreet_Desktop *efreet_util_desktop_name_find(const char *name); + +/** + * Find a desktop by generic name + * + * return value must be freed by efreet_desktop_free + * + * @param generic_name the generic name + * @return a desktop + */ +EAPI Efreet_Desktop *efreet_util_desktop_generic_name_find(const char *generic_name); + + +/** + * Find all desktops where name matches a glob pattern + * + * This list must be freed using EINA_LIST_FREE / efreet_desktop_free + * + * @param glob the pattern to match + * @return a list of desktops + */ +EAPI Eina_List *efreet_util_desktop_name_glob_list(const char *glob); + +/** + * Find all desktops where exec matches a glob pattern + * + * This list must be freed using EINA_LIST_FREE / efreet_desktop_free + * + * @param glob the pattern to match + * @return a list of desktops + */ +EAPI Eina_List *efreet_util_desktop_exec_glob_list(const char *glob); + +/** + * Find all desktops where generic name matches a glob pattern + * + * This list must be freed using EINA_LIST_FREE / efreet_desktop_free + * + * @param glob the pattern to match + * @return a list of desktops + */ +EAPI Eina_List *efreet_util_desktop_generic_name_glob_list(const char *glob); + +/** + * Find all desktops where comment matches a glob pattern + * + * This list must be freed using EINA_LIST_FREE / efreet_desktop_free + * + * @param glob the pattern to match + * @return a list of desktops + */ +EAPI Eina_List *efreet_util_desktop_comment_glob_list(const char *glob); + + +/** + * Find all desktop categories + * This list must be freed using EINA_LIST_FREE + * + * @return an Eina_List of category names (const char *) + */ +EAPI Eina_List *efreet_util_desktop_categories_list(void); + +/** + * Find all desktops in a given category + * + * This list must be freed using EINA_LIST_FREE / efreet_desktop_free + * + * @param category the category name + * @return a list of desktops + */ +EAPI Eina_List *efreet_util_desktop_category_list(const char *category); + + +/** + * Returns a list of .menu files found in the various config dirs. + * @return An eina list of menu file paths (const char *). This must be freed with EINA_LIST_FREE. + */ +EAPI Eina_List *efreet_util_menus_find(void); + +/** + * @} + */ +#endif diff --git a/src/lib/efreet_xml.c b/src/lib/efreet_xml.c new file mode 100644 index 0000000..9767d75 --- /dev/null +++ b/src/lib/efreet_xml.c @@ -0,0 +1,609 @@ +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include +#include + +#include + +/* define macros and variable for using the eina logging system */ +#define EFREET_MODULE_LOG_DOM _efreet_xml_log_dom +static int _efreet_xml_log_dom = -1; + +#include "Efreet.h" +#include "efreet_private.h" +#include "efreet_xml.h" + +#if 0 +static void efreet_xml_dump(Efreet_Xml *xml, int level); +#endif + +static Efreet_Xml *efreet_xml_parse(char **data, int *size); +static int efreet_xml_tag_parse(char **data, int *size, const char **tag); +static void efreet_xml_attributes_parse(char **data, int *size, + Efreet_Xml_Attribute ***attributes); +static void efreet_xml_text_parse(char **data, int *size, const char **text); + +static int efreet_xml_tag_empty(char **data, int *size); +static int efreet_xml_tag_close(char **data, int *size, const char *tag); + +static void efreet_xml_cb_attribute_free(void *data); +static void efreet_xml_comment_skip(char **data, int *size); + +static int error = 0; + +static int _efreet_xml_init_count = 0; + +/** + * @internal + * @return Returns > 0 on success or 0 on failure + * @brief Initialize the XML parser subsystem + */ +int +efreet_xml_init(void) +{ + _efreet_xml_init_count++; + if (_efreet_xml_init_count > 1) return _efreet_xml_init_count; + _efreet_xml_log_dom = eina_log_domain_register + ("efreet_xml", EFREET_DEFAULT_LOG_COLOR); + if (_efreet_xml_log_dom < 0) + { + _efreet_xml_init_count--; + EINA_LOG_ERR("Efreet: Could not create a log domain for efreet_xml."); + return _efreet_xml_init_count; + } + return _efreet_xml_init_count; +} + +/** + * @internal + * @returns the number of initializations left for this system + * @brief Attempts to shut down the subsystem if nothing else is using it + */ +void +efreet_xml_shutdown(void) +{ + _efreet_xml_init_count--; + if (_efreet_xml_init_count > 0) return; + eina_log_domain_unregister(_efreet_xml_log_dom); + _efreet_xml_log_dom = -1; +} + +/** + * @internal + * @param file The file to parse + * @return Returns an Efreet_Xml structure for the given file @a file or + * NULL on failure + * @brief Parses the given file into an Efreet_Xml structure. + */ +Efreet_Xml * +efreet_xml_new(const char *file) +{ + Efreet_Xml *xml = NULL; + int size, fd = -1; + char *data = MAP_FAILED; + + if (!file) return NULL; + if (!ecore_file_exists(file)) return NULL; + + size = ecore_file_size(file); + if (size <= 0) goto efreet_error; + + fd = open(file, O_RDONLY); + if (fd == -1) goto efreet_error; + + /* let's make mmap safe and just get 0 pages for IO erro */ + eina_mmap_safety_enabled_set(EINA_TRUE); + + data = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) goto efreet_error; + + error = 0; + xml = efreet_xml_parse(&data, &size); + if (!xml || error) goto efreet_error; + + munmap(data, size); + close(fd); + return xml; + +efreet_error: + ERR("could not parse xml file"); + if (data != MAP_FAILED) munmap(data, size); + if (fd != -1) close(fd); + if (xml) efreet_xml_del(xml); + return NULL; +} + +/** + * @internal + * @param xml The Efree_Xml to free + * @return Returns no value + * @brief Frees up the given Efreet_Xml structure + */ +void +efreet_xml_del(Efreet_Xml *xml) +{ + IF_FREE_LIST(xml->children, efreet_xml_cb_attribute_free); + + if (xml->tag) eina_stringshare_del(xml->tag); + if (xml->attributes) + { + Efreet_Xml_Attribute **curr; + + curr = xml->attributes; + while (*curr) + { + eina_stringshare_del((*curr)->key); + eina_stringshare_del((*curr)->value); + + FREE(*curr); + curr++; + } + FREE(xml->attributes); + } + IF_RELEASE(xml->text); + FREE(xml); +} + +/** + * @param xml The xml struct to work with + * @param key The attribute key to look for + * @return Returns the value for the given key, or NULL if none found + * @brief Retrieves the value for the given attribute key + */ +const char * +efreet_xml_attribute_get(Efreet_Xml *xml, const char *key) +{ + Efreet_Xml_Attribute **curr; + + if (!xml || !key || !xml->attributes) return NULL; + + for (curr = xml->attributes; *curr; curr++) + { + if (!strcmp((*curr)->key, key)) + return (*curr)->value; + } + return NULL; +} + +static void +efreet_xml_cb_attribute_free(void *data) +{ + efreet_xml_del(data); +} + +#if 0 +static void +efreet_xml_dump(Efreet_Xml *xml, int level) +{ + int i; + + for (i = 0; i < level; i++) + printf("\t"); + printf("<%s", xml->tag); + if (xml->attributes) + { + Efreet_Xml_Attribute **curr; + for (curr = xml->attributes; *curr; curr++) + printf(" %s=\"%s\"", (*curr)->key, (*curr)->value); + } + + if (xml->children) + { + Efreet_Xml *child; + Eina_List *l; + + printf(">"); + + EINA_LIST_FOREACH(xml->children, l, child) + efreet_xml_dump(child, level + 1); + + for (i = 0; i < level; i++) + printf("\t"); + printf("", xml->tag); + } + else if (xml->text) + printf(">%s\n", xml->text, xml->tag); + else + printf("/>\n"); +} +#endif + +static Efreet_Xml * +efreet_xml_parse(char **data, int *size) +{ + Efreet_Xml *xml, *sub_xml; + const char *tag = NULL; + + /* parse this tag */ + if (!efreet_xml_tag_parse(data, size, &(tag))) return NULL; + xml = NEW(Efreet_Xml, 1); + if (!xml) + { + eina_stringshare_del(tag); + return NULL; + } + + xml->children = NULL; + + xml->tag = tag; + efreet_xml_attributes_parse(data, size, &(xml->attributes)); + + /* Check wether element is empty */ + if (efreet_xml_tag_empty(data, size)) return xml; + efreet_xml_text_parse(data, size, &(xml->text)); + + /* Check wether element is closed */ + if (efreet_xml_tag_close(data, size, xml->tag)) return xml; + + while ((sub_xml = efreet_xml_parse(data, size))) + xml->children = eina_list_append(xml->children, sub_xml); + + efreet_xml_tag_close(data, size, xml->tag); + + return xml; +} + +static int +efreet_xml_tag_parse(char **data, int *size, const char **tag) +{ + const char *start = NULL, *end = NULL; + char buf[256]; + int buf_size; + + /* Search for tag */ + while (*size > 1) + { + /* Check for tag start */ + if (**data == '<') + { + /* Check for end tag */ + if (*(*data + 1) == '/') return 0; + + /* skip comments */ + if (*size > 3 && *(*data + 1) == '!' && *(*data + 2) == '-' && *(*data + 3) == '-') + { + (*data) += 3; + (*size) -= 3; + efreet_xml_comment_skip(data, size); + continue; + } + + /* Check for xml directives (and ignore them) */ + else if ((*(*data + 1) != '!') && (*(*data + 1) != '?')) + { + (*size)--; + (*data)++; + start = *data; + break; + } + } + (*size)--; + (*data)++; + } + + if (!start) + { + ERR("missing start tag"); + error = 1; + return 0; + } + + while (*size > 0) + { + if (!isalpha(**data)) + { + end = *data; + break; + } + (*size)--; + (*data)++; + } + + if (!end) + { + ERR("no end of tag"); + error = 1; + return 0; + } + + buf_size = end - start + 1; + if (buf_size <= 1) + { + ERR("no tag name"); + error = 1; + return 0; + } + + if (buf_size > 256) buf_size = 256; + memcpy(buf, start, buf_size - 1); + buf[buf_size - 1] = '\0'; + *tag = eina_stringshare_add(buf); + + return 1; +} + +static void +efreet_xml_attributes_parse(char **data, int *size, + Efreet_Xml_Attribute ***attributes) +{ + Efreet_Xml_Attribute attr[10]; + int i, count = 0; + + while (*size > 0) + { + if (**data == '>') + { + (*size)++; + (*data)--; + break; + } + else if ((count < 10) && (isalpha(**data))) + { + /* beginning of key */ + const char *start = NULL, *end = NULL; + char buf[256]; + int buf_size; + + attr[count].key = NULL; + attr[count].value = NULL; + + start = *data; + while ((*size > 0) && ((isalpha(**data)) || (**data == '_'))) + { + (*size)--; + (*data)++; + } + + end = *data; + buf_size = end - start + 1; + if (buf_size <= 1) + { + ERR("zero length key"); + goto efreet_error; + } + + if (buf_size > 256) buf_size = 256; + memcpy(buf, start, buf_size - 1); + buf[buf_size - 1] = '\0'; + attr[count].key = eina_stringshare_add(buf); + + /* search for '=', key/value seperator */ + start = NULL; + while (*size > 0) + { + if (**data == '=') + { + start = *data; + break; + } + (*size)--; + (*data)++; + } + + if (!start) + { + ERR("missing value for attribute!"); + goto efreet_error; + } + + /* search for '"', beginning of value */ + start = NULL; + while (*size > 0) + { + if (**data == '"') + { + start = *data; + break; + } + (*size)--; + (*data)++; + } + + if (!start) + { + ERR("erroneous value for attribute!"); + goto efreet_error; + } + + /* skip '"' */ + start++; + (*size)--; + (*data)++; + + /* search for '"', end of value */ + end = NULL; + while (*size > 0) + { + if (**data == '"') + { + end = *data; + break; + } + (*size)--; + (*data)++; + } + + if (!end) + { + ERR("erroneous value for attribute!"); + goto efreet_error; + } + + buf_size = end - start + 1; + if (buf_size <= 1) + { + ERR("zero length value"); + goto efreet_error; + } + + if (buf_size > 256) buf_size = 256; + memcpy(buf, start, buf_size - 1); + buf[buf_size - 1] = '\0'; + attr[count].value = eina_stringshare_add(buf); + + count++; + } + + (*size)--; + (*data)++; + } + + *attributes = NEW(Efreet_Xml_Attribute *, count + 1); + if (!*attributes) goto efreet_error; + for (i = 0; i < count; i++) + { + (*attributes)[i] = malloc(sizeof(Efreet_Xml_Attribute)); + (*attributes)[i]->key = attr[i].key; + (*attributes)[i]->value = attr[i].value; + } + return; + +efreet_error: + while (count >= 0) + { + if (attr[count].key) eina_stringshare_del(attr[count].key); + if (attr[count].value) eina_stringshare_del(attr[count].value); + count--; + } + error = 1; + return; +} + +static void +efreet_xml_text_parse(char **data, int *size, const char **text) +{ + const char *start = NULL, *end = NULL; + int buf_size; + + /* skip leading whitespace */ + while (*size > 0) + { + if (!isspace(**data)) + { + start = *data; + break; + } + (*size)--; + (*data)++; + } + + if (!start) return; + + /* find next tag */ + while (*size > 0) + { + if (**data == '<') + { + end = *data; + break; + } + (*size)--; + (*data)++; + } + if (!end) return; + + /* skip trailing whitespace */ + while (isspace(*(end - 1))) end--; + + /* copy text */ + buf_size = end - start + 1; + if (buf_size <= 1) return; + + *text = eina_stringshare_add_length(start, buf_size - 1); +} + +static int +efreet_xml_tag_empty(char **data, int *size) +{ + while (*size > 1) + { + if (**data == '/') + { + (*size)--; + (*data)++; + if (**data == '>') + { + (*size)--; + (*data)++; + return 1; + } + } + else if (**data == '>') + { + (*size)--; + (*data)++; + return 0; + } + (*size)--; + (*data)++; + } + ERR("missing end of tag"); + error = 1; + + return 1; +} + +static int +efreet_xml_tag_close(char **data, int *size, const char *tag) +{ + while (*size > 1) + { + if (**data == '<') + { + if (*(*data + 1) == '/') + { + (*size) -= 2; + (*data) += 2; + if ((int)strlen(tag) > *size) + { + ERR("wrong end tag"); + error = 1; + return 1; + } + else + { + char *tmp; + tmp = *data; + while ((*tag) && (*tmp == *tag)) + { + tmp++; + tag++; + } + + if (*tag) + { + ERR("wrong end tag"); + error = 1; + return 1; + } + } + return 1; + } + else return 0; + } + (*size)--; + (*data)++; + } + return 0; +} + +static void +efreet_xml_comment_skip(char **data, int *size) +{ + while (*size > 2) + { + if (**data == '-' && *(*data + 1) == '-' && *(*data + 2) == '>') + { + (*data) += 3; + (*size) -= 3; + return; + } + (*data)++; + (*size)--; + } +} diff --git a/src/lib/efreet_xml.h b/src/lib/efreet_xml.h new file mode 100644 index 0000000..77473cc --- /dev/null +++ b/src/lib/efreet_xml.h @@ -0,0 +1,59 @@ +#ifndef EFREET_XML_H +#define EFREET_XML_H + +/** + * @internal + * @file efreet_xml.h + * @brief A simple and fast XML parser + * @addtogroup Efreet_Xml Efreet_Xml: An XML parser + * + * @{ + */ + +/** + * Efreet_Xml_Attributes + */ +typedef struct Efreet_Xml_Attribute Efreet_Xml_Attribute; + +/** + * Efreet_Xml_Attributes + * @brief Contains information about a given XML attribute + */ +struct Efreet_Xml_Attribute +{ + const char *key; /**< The attribute key */ + const char *value; /**< The attribute value */ +}; + +/** + * Efreet_Xml + */ +typedef struct Efreet_Xml Efreet_Xml; + +/** + * Efreet_Xml + * @brief Contains the XML tree for a given XML document + */ +struct Efreet_Xml +{ + const char *text; /**< The XML text for this node */ + const char *tag; /**< The tag for this node */ + + Efreet_Xml_Attribute **attributes; /**< The attributes for this node */ + + Eina_List *children; /**< Child nodes */ +}; + +int efreet_xml_init(void); +void efreet_xml_shutdown(void); + +Efreet_Xml *efreet_xml_new(const char *file); +void efreet_xml_del(Efreet_Xml *xml); + +const char *efreet_xml_attribute_get(Efreet_Xml *xml, const char *key); + +/** + * @} + */ + +#endif diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am new file mode 100644 index 0000000..13c1274 --- /dev/null +++ b/src/tests/Makefile.am @@ -0,0 +1,66 @@ + +SUBDIRS = data compare + +MAINTAINERCLEANFILES = Makefile.in + +AM_CPPFLAGS = \ +-I. \ +-I$(top_srcdir)/src/lib \ +-DPACKAGE_BIN_DIR=\"$(bindir)\" \ +-DPACKAGE_LIB_DIR=\"$(libdir)\" \ +-DPACKAGE_DATA_DIR=\"$(datadir)\" \ +-DPKG_DATA_DIR=\"$(pkgdatadir)\" \ +@EFREET_CFLAGS@ + +bin_PROGRAMS = \ +efreet_test \ +efreet_spec_test \ +efreet_cache_test \ +efreet_icon_cache_dump + +efreet_test_LDADD = $(top_builddir)/src/lib/libefreet.la \ + $(top_builddir)/src/lib/libefreet_mime.la \ + @EFREET_LIBS@ +efreet_test_SOURCES = \ +ef_test.h \ +ef_data_dirs.c \ +ef_icon_theme.c \ +ef_ini.c \ +ef_utils.c \ +ef_desktop.c \ +ef_menu.c \ +ef_mime.c \ +main.c + +if DEFAULT_VISIBILITY +efreet_test_SOURCES += \ +ef_locale.c +endif + +efreet_spec_test_LDADD = $(top_builddir)/src/lib/libefreet.la @EFREET_LIBS@ +efreet_spec_test_SOURCES = \ +efreet_spec_test.c + +efreet_cache_test_LDADD = $(top_builddir)/src/lib/libefreet.la @EFREET_LIBS@ +efreet_cache_test_SOURCES = \ +ef_cache.c + +if EFL_ENABLE_TESTS + +check_PROGRAMS = efreet_suite + +efreet_suite_SOURCES = \ +efreet_suite.c \ +efreet_test_efreet.c \ +efreet_test_efreet_cache.c + +efreet_suite_LDADD = @CHECK_LIBS@ $(top_builddir)/src/lib/libefreet.la @EFREET_LIBS@ + +endif + +efreet_icon_cache_dump_LDADD = \ +$(top_builddir)/src/lib/libefreet.la \ +@EFREET_LIBS@ + +efreet_icon_cache_dump_SOURCES = \ +efreet_icon_cache_dump.c diff --git a/src/tests/compare/Makefile.am b/src/tests/compare/Makefile.am new file mode 100644 index 0000000..bb43591 --- /dev/null +++ b/src/tests/compare/Makefile.am @@ -0,0 +1,15 @@ +MAINTAINERCLEANFILES = Makefile.in + +AM_CPPFLAGS = \ +-I. \ +-I$(top_srcdir)/src/lib \ +@EFREET_CFLAGS@ + +bin_PROGRAMS = efreet_alloc efreet_menu_alloc + +efreet_menu_alloc_SOURCES = efreet_menu_alloc.c comp.h +efreet_menu_alloc_LDADD = $(top_builddir)/src/lib/libefreet.la @EFREET_LIBS@ + +efreet_alloc_SOURCES = efreet_alloc.c comp.h +efreet_alloc_LDADD = $(top_builddir)/src/lib/libefreet.la @EFREET_LIBS@ + diff --git a/src/tests/compare/comp.h b/src/tests/compare/comp.h new file mode 100644 index 0000000..c02eebe --- /dev/null +++ b/src/tests/compare/comp.h @@ -0,0 +1,527 @@ +#ifndef COMP_H +#define COMP_H + +#define LOOPS 1000 +#define THEME "Tango" +#define SIZE 16 + +#define ADDRESS_BOOK_NEW "address-book-new" +#define APPLICATION_EXIT "application-exit" +#define APPOINTMENT_NEW "appointment-new" +#define CONTACT_NEW "contact-new" +#define DIALOG_APPLY "dialog-apply" +#define DIALOG_CANCEL "dialog-cancel" +#define DIALOG_CLOSE "dialog-close" +#define DIALOG_OK "dialog-ok" +#define DOCUMENT_NEW "document-new" +#define DOCUMENT_OPEN "document-open" +#define DOCUMENT_OPEN_RECENT "document-open-recent" +#define DOCUMENT_PAGE_SETUP "document-page-setup" +#define DOCUMENT_PRINT "document-print" +#define DOCUMENT_PRINT_PREVIEW "document-print-preview" +#define DOCUMENT_PROPERTIES "document-properties" +#define DOCUMENT_REVERT "document-revert" +#define DOCUMENT_SAVE "document-save" +#define DOCUMENT_SAVE_AS "document-save-as" +#define EDIT_COPY "edit-copy" +#define EDIT_CUT "edit-cut" +#define EDIT_DELETE "edit-delete" +#define EDIT_FIND "edit-find" +#define EDIT_FIND_REPLACE "edit-find-replace" +#define EDIT_PASTE "edit-paste" +#define EDIT_REDO "edit-redo" +#define EDIT_SELECT_ALL "edit-select-all" +#define EDIT_UNDO "edit-undo" +#define FORMAT_INDENT_LESS "format-indent-less" +#define FORMAT_INDENT_MORE "format-indent-more" +#define FORMAT_JUSTIFY_CENTER "format-justify-center" +#define FORMAT_JUSTIFY_FILL "format-justify-fill" +#define FORMAT_JUSTIFY_LEFT "format-justify-left" +#define FORMAT_JUSTIFY_RIGHT "format-justify-right" +#define FORMAT_TEXT_DIRECTION_LTR "format-text-direction-ltr" +#define FORMAT_TEXT_DIRECTION_RTL "format-text-direction-rtl" +#define FORMAT_TEXT_BOLD "format-text-bold" +#define FORMAT_TEXT_ITALIC "format-text-italic" +#define FORMAT_TEXT_UNDERLINE "format-text-underline" +#define FORMAT_TEXT_STRIKETHROUGH "format-text-strikethrough" +#define GO_BOTTOM "go-bottom" +#define GO_DOWN "go-down" +#define GO_FIRST "go-first" +#define GO_HOME "go-home" +#define GO_JUMP "go-jump" +#define GO_LAST "go-last" +#define GO_NEXT "go-next" +#define GO_PREVIOUS "go-previous" +#define GO_TOP "go-top" +#define GO_UP "go-up" +#define HELP_ABOUT "help-about" +#define HELP_CONTENTS "help-contents" +#define HELP_FAQ "help-faq" +#define INSERT_IMAGE "insert-image" +#define INSERT_LINK "insert-link" +#define INSERT_OBJECT "insert-object" +#define INSERT_TEXT "insert-text" +#define LIST_ADD "list-add" +#define LIST_REMOVE "list-remove" +#define MAIL_FORWARD "mail-forward" +#define MAIL_MARK_IMPORTANT "mail-mark-important" +#define MAIL_MARK_JUNK "mail-mark-junk" +#define MAIL_MARK_NOTJUNK "mail-mark-notjunk" +#define MAIL_MARK_READ "mail-mark-read" +#define MAIL_MARK_UNREAD "mail-mark-unread" +#define MAIL_MESSAGE_NEW "mail-message-new" +#define MAIL_REPLY_ALL "mail-reply-all" +#define MAIL_REPLY_SENDER "mail-reply-sender" +#define MAIL_SEND_RECEIVE "mail-send-receive" +#define MEDIA_EJECT "media-eject" +#define MEDIA_PLAYBACK_PAUSE "media-playback-pause" +#define MEDIA_PLAYBACK_START "media-playback-start" +#define MEDIA_PLAYBACK_STOP "media-playback-stop" +#define MEDIA_RECORD "media-record" +#define MEDIA_SEEK_BACKWARD "media-seek-backward" +#define MEDIA_SEEK_FORWARD "media-seek-forward" +#define MEDIA_SKIP_BACKWARD "media-skip-backward" +#define MEDIA_SKIP_FORWARD "media-skip-forward" +#define SYSTEM_LOCK_SCREEN "system-lock-screen" +#define SYSTEM_LOG_OUT "system-log-out" +#define SYSTEM_RUN "system-run" +#define SYSTEM_SEARCH "system-search" +#define TOOLS_CHECK_SPELLING "tools-check-spelling" +#define VIEW_FULLSCREEN "view-fullscreen" +#define VIEW_REFRESH "view-refresh" +#define VIEW_SORT_ASCENDING "view-sort-ascending" +#define VIEW_SORT_DESCENDING "view-sort-descending" +#define WINDOW_CLOSE "window-close" +#define WINDOW_NEW "window-new" +#define ZOOM_BEST_FIT "zoom-best-fit" +#define ZOOM_IN "zoom-in" +#define ZOOM_ORIGINAL "zoom-original" +#define ZOOM_OUT "zoom-out" + +#define PROCESS_WORKING "process-working" + +#define ACCESSORIES_CALCULATOR "accessories-calculator" +#define ACCESSORIES_CHARACTER_MAP "accessories-character-map" +#define ACCESSORIES_DICTIONARY "accessories-dictionary" +#define ACCESSORIES_TEXT_EDITOR "accessories-text-editor" +#define HELP_BROWSER "help-browser" +#define MULTIMEDIA_VOLUME_CONTROL "multimedia-volume-control" +#define PREFERENCES_DESKTOP_ACCESSIBILITY "preferences-desktop-accessibility" +#define PREFERENCES_DESKTOP_FONT "preferences-desktop-font" +#define PREFERENCES_DESKTOP_KEYBOARD "preferences-desktop-keyboard" +#define PREFERENCES_DESKTOP_LOCALE "preferences-desktop-locale" +#define PREFERENCES_DESKTOP_MULTIMEDIA "preferences-desktop-multimedia" +#define PREFERENCES_DESKTOP_SCREENSAVER "preferences-desktop-screensaver" +#define PREFERENCES_DESKTOP_THEME "preferences-desktop-theme" +#define PREFERENCES_DESKTOP_WALLPAPER "preferences-desktop-wallpaper" +#define SYSTEM_FILE_MANAGER "system-file-manager" +#define SYSTEM_SOFTWARE_UPDATE "system-software-update" +#define UTILITIES_TERMINAL "utilities-terminal" + +#define APPLICATIONS_ACCESSORIES "applications-accessories" +#define APPLICATIONS_DEVELOPMENT "applications-development" +#define APPLICATIONS_GAMES "applications-games" +#define APPLICATIONS_GRAPHICS "applications-graphics" +#define APPLICATIONS_INTERNET "applications-internet" +#define APPLICATIONS_MULTIMEDIA "applications-multimedia" +#define APPLICATIONS_OFFICE "applications-office" +#define APPLICATIONS_OTHER "applications-other" +#define APPLICATIONS_SYSTEM "applications-system" +#define APPLICATIONS_UTILITIES "applications-utilities" +#define PREFERENCES_DESKTOP "preferences-desktop" +#define PREFERENCES_DESKTOP_ACCESSIBILITY "preferences-desktop-accessibility" +#define PREFERENCES_DESKTOP_PERIPHERALS "preferences-desktop-peripherals" +#define PREFERENCES_DESKTOP_PERSONAL "preferences-desktop-personal" +#define PREFERENCES_OTHER "preferences-other" +#define PREFERENCES_SYSTEM "preferences-system" +#define PREFERENCES_SYSTEM_NETWORK "preferences-system-network" +#define SYSTEM_HELP "system-help" + +#define AUDIO_CARD "audio-card" +#define AUDIO_INPUT_MICROPHONE "audio-input-microphone" +#define BATTERY "battery" +#define CAMERA_PHOTO "camera-photo" +#define CAMERA_VIDEO "camera-video" +#define COMPUTER "computer" +#define DRIVE_CDROM "drive-cdrom" +#define DRIVE_HARDDISK "drive-harddisk" +#define DRIVE_REMOVABLE_MEDIA "drive-removable-media" +#define INPUT_GAMING "input-gaming" +#define INPUT_KEYBOARD "input-keyboard" +#define INPUT_MOUSE "input-mouse" +#define MEDIA_CDROM "media-cdrom" +#define MEDIA_FLOPPY "media-floppy" +#define MULTIMEDIA_PLAYER "multimedia-player" +#define NETWORK_WIRED "network-wired" +#define NETWORK_WIRELESS "network-wireless" +#define PRINTER "printer" + +#define EMBLEM_DEFAULT "emblem-default" +#define EMBLEM_DOCUMENTS "emblem-documents" +#define EMBLEM_DOWNLOADS "emblem-downloads" +#define EMBLEM_FAVORITE "emblem-favorite" +#define EMBLEM_IMPORTANT "emblem-important" +#define EMBLEM_MAIL "emblem-mail" +#define EMBLEM_PHOTOS "emblem-photos" +#define EMBLEM_READONLY "emblem-readonly" +#define EMBLEM_SHARED "emblem-shared" +#define EMBLEM_SYMBOLIC_LINK "emblem-symbolic-link" +#define EMBLEM_SYNCHRONIZED "emblem-synchronized" +#define EMBLEM_SYSTEM "emblem-system" +#define EMBLEM_UNREADABLE "emblem-unreadable" + +#define FACE_ANGEL "face-angel" +#define FACE_CRYING "face-crying" +#define FACE_DEVIL_GRIN "face-devil-grin" +#define FACE_DEVIL_SAD "face-devil-sad" +#define FACE_GLASSES "face-glasses" +#define FACE_KISS "face-kiss" +#define FACE_MONKEY "face-monkey" +#define FACE_PLAIN "face-plain" +#define FACE_SAD "face-sad" +#define FACE_SMILE "face-smile" +#define FACE_SMILE_BIG "face-smile-big" +#define FACE_SMIRK "face-smirk" +#define FACE_SURPRISE "face-surprise" +#define FACE_WINK "face-wink" + +#define APPLICATION_X_EXECUTABLE "application-x-executable" +#define AUDIO_X_GENERIC "audio-x-generic" +#define FONT_X_GENERIC "font-x-generic" +#define IMAGE_X_GENERIC "image-x-generic" +#define PACKAGE_X_GENERIC "package-x-generic" +#define TEXT_HTML "text-html" +#define TEXT_X_GENERIC "text-x-generic" +#define TEXT_X_GENERIC_TEMPLATE "text-x-generic-template" +#define TEXT_X_SCRIPT "text-x-script" +#define VIDEO_X_GENERIC "video-x-generic" +#define X_OFFICE_ADDRESS_BOOK "x-office-address-book" +#define X_OFFICE_CALENDAR "x-office-calendar" +#define X_OFFICE_DOCUMENT "x-office-document" +#define X_OFFICE_PRESENTATION "x-office-presentation" +#define X_OFFICE_SPREADSHEET "x-office-spreadsheet" + +#define FOLDER "folder" +#define FOLDER_REMOTE "folder-remote" +#define NETWORK_SERVER "network-server" +#define NETWORK_WORKGROUP "network-workgroup" +#define START_HERE "start-here" +#define USER_DESKTOP "user-desktop" +#define USER_HOME "user-home" +#define USER_TRASH "user-trash" + +#define APPOINTMENT_MISSED "appointment-missed" +#define APPOINTMENT_SOON "appointment-soon" +#define AUDIO_VOLUME_HIGH "audio-volume-high" +#define AUDIO_VOLUME_LOW "audio-volume-low" +#define AUDIO_VOLUME_MEDIUM "audio-volume-medium" +#define AUDIO_VOLUME_MUTED "audio-volume-muted" +#define BATTERY_CAUTION "battery-caution" +#define BATTERY_LOW "battery-low" +#define DIALOG_ERROR "dialog-error" +#define DIALOG_INFORMATION "dialog-information" +#define DIALOG_PASSWORD "dialog-password" +#define DIALOG_QUESTION "dialog-question" +#define DIALOG_WARNING "dialog-warning" +#define FOLDER_DRAG_ACCEPT "folder-drag-accept" +#define FOLDER_OPEN "folder-open" +#define FOLDER_VISITING "folder-visiting" +#define IMAGE_LOADING "image-loading" +#define IMAGE_MISSING "image-missing" +#define MAIL_ATTACHMENT "mail-attachment" +#define MAIL_UNREAD "mail-unread" +#define MAIL_READ "mail-read" +#define MAIL_REPLIED "mail-replied" +#define MAIL_SIGNED "mail-signed" +#define MAIL_SIGNED_VERIFIED "mail-signed-verified" +#define MEDIA_PLAYLIST_REPEAT "media-playlist-repeat" +#define MEDIA_PLAYLIST_SHUFFLE "media-playlist-shuffle" +#define NETWORK_ERROR "network-error" +#define NETWORK_IDLE "network-idle" + +#define NETWORK_OFFLINE "network-offline" +#define NETWORK_RECEIVE "network-receive" +#define NETWORK_TRANSMIT "network-transmit" +#define NETWORK_TRANSMIT_RECEIVE "network-transmit-receive" +#define PRINTER_ERROR "printer-error" +#define PRINTER_PRINTING "printer-printing" +#define SOFTWARE_UPDATE_AVAILABLE "software-update-available" +#define SOFTWARE_UPDATE_URGENT "software-update-urgent" +#define SYNC_ERROR "sync-error" +#define SYNC_SYNCHRONIZING "sync-synchronizing" +#define TASK_DUE "task-due" +#define TASK_PASSED_DUE "task-passed-due" +#define USER_AWAY "user-away" +#define USER_IDLE "user-idle" +#define USER_OFFLINE "user-offline" +#define USER_ONLINE "user-online" +#define USER_TRASH_FULL "user-trash-full" +#define WEATHER_CLEAR "weather-clear" +#define WEATHER_CLEAR_NIGHT "weather-clear-night" +#define WEATHER_FEW_CLOUDS "weather-few-clouds" +#define WEATHER_FEW_CLOUDS_NIGHT "weather-few-clouds-night" +#define WEATHER_FOG "weather-fog" +#define WEATHER_OVERCAST "weather-overcast" +#define WEATHER_SEVERE_ALERT "weather-severe-alert" +#define WEATHER_SHOWERS "weather-showers" +#define WEATHER_SHOWERS_SCATTERED "weather-showers-scattered" +#define WEATHER_SNOW "weather-snow" +#define WEATHER_STORM "weather-storm" + +const char *icons[] = { + ADDRESS_BOOK_NEW, + APPLICATION_EXIT, + APPOINTMENT_NEW, + CONTACT_NEW, + DIALOG_APPLY, + DIALOG_CANCEL, + DIALOG_CLOSE, + DIALOG_OK, + DOCUMENT_NEW, + DOCUMENT_OPEN, + DOCUMENT_OPEN_RECENT, + DOCUMENT_PAGE_SETUP, + DOCUMENT_PRINT, + DOCUMENT_PRINT_PREVIEW, + DOCUMENT_PROPERTIES, + DOCUMENT_REVERT, + DOCUMENT_SAVE, + DOCUMENT_SAVE_AS, + EDIT_COPY, + EDIT_CUT, + EDIT_DELETE, + EDIT_FIND, + EDIT_FIND_REPLACE, + EDIT_PASTE, + EDIT_REDO, + EDIT_SELECT_ALL, + EDIT_UNDO, + FORMAT_INDENT_LESS, + FORMAT_INDENT_MORE, + FORMAT_JUSTIFY_CENTER, + FORMAT_JUSTIFY_FILL, + FORMAT_JUSTIFY_LEFT, + FORMAT_JUSTIFY_RIGHT, + FORMAT_TEXT_DIRECTION_LTR, + FORMAT_TEXT_DIRECTION_RTL, + FORMAT_TEXT_BOLD, + FORMAT_TEXT_ITALIC, + FORMAT_TEXT_UNDERLINE, + FORMAT_TEXT_STRIKETHROUGH, + GO_BOTTOM, + GO_DOWN, + GO_FIRST, + GO_HOME, + GO_JUMP, + GO_LAST, + GO_NEXT, + GO_PREVIOUS, + GO_TOP, + GO_UP, + HELP_ABOUT, + HELP_CONTENTS, + HELP_FAQ, + INSERT_IMAGE, + INSERT_LINK, + INSERT_OBJECT, + INSERT_TEXT, + LIST_ADD, + LIST_REMOVE, + MAIL_FORWARD, + MAIL_MARK_IMPORTANT, + MAIL_MARK_JUNK, + MAIL_MARK_NOTJUNK, + MAIL_MARK_READ, + MAIL_MARK_UNREAD, + MAIL_MESSAGE_NEW, + MAIL_REPLY_ALL, + MAIL_REPLY_SENDER, + MAIL_SEND_RECEIVE, + MEDIA_EJECT, + MEDIA_PLAYBACK_PAUSE, + MEDIA_PLAYBACK_START, + MEDIA_PLAYBACK_STOP, + MEDIA_RECORD, + MEDIA_SEEK_BACKWARD, + MEDIA_SEEK_FORWARD, + MEDIA_SKIP_BACKWARD, + MEDIA_SKIP_FORWARD, + SYSTEM_LOCK_SCREEN, + SYSTEM_LOG_OUT, + SYSTEM_RUN, + SYSTEM_SEARCH, + TOOLS_CHECK_SPELLING, + VIEW_FULLSCREEN, + VIEW_REFRESH, + VIEW_SORT_ASCENDING, + VIEW_SORT_DESCENDING, + WINDOW_CLOSE, + WINDOW_NEW, + ZOOM_BEST_FIT, + ZOOM_IN, + ZOOM_ORIGINAL, + ZOOM_OUT, + PROCESS_WORKING, + ACCESSORIES_CALCULATOR, + ACCESSORIES_CHARACTER_MAP, + ACCESSORIES_DICTIONARY, + ACCESSORIES_TEXT_EDITOR, + HELP_BROWSER, + MULTIMEDIA_VOLUME_CONTROL, + PREFERENCES_DESKTOP_ACCESSIBILITY, + PREFERENCES_DESKTOP_FONT, + PREFERENCES_DESKTOP_KEYBOARD, + PREFERENCES_DESKTOP_LOCALE, + PREFERENCES_DESKTOP_MULTIMEDIA, + PREFERENCES_DESKTOP_SCREENSAVER, + PREFERENCES_DESKTOP_THEME, + PREFERENCES_DESKTOP_WALLPAPER, + SYSTEM_FILE_MANAGER, + SYSTEM_SOFTWARE_UPDATE, + UTILITIES_TERMINAL, + APPLICATIONS_ACCESSORIES, + APPLICATIONS_DEVELOPMENT, + APPLICATIONS_GAMES, + APPLICATIONS_GRAPHICS, + APPLICATIONS_INTERNET, + APPLICATIONS_MULTIMEDIA, + APPLICATIONS_OFFICE, + APPLICATIONS_OTHER, + APPLICATIONS_SYSTEM, + APPLICATIONS_UTILITIES, + PREFERENCES_DESKTOP, + PREFERENCES_DESKTOP_ACCESSIBILITY, + PREFERENCES_DESKTOP_PERIPHERALS, + PREFERENCES_DESKTOP_PERSONAL, + PREFERENCES_OTHER, + PREFERENCES_SYSTEM, + PREFERENCES_SYSTEM_NETWORK, + SYSTEM_HELP, + AUDIO_CARD, + AUDIO_INPUT_MICROPHONE, + BATTERY, + CAMERA_PHOTO, + CAMERA_VIDEO, + COMPUTER, + DRIVE_CDROM, + DRIVE_HARDDISK, + DRIVE_REMOVABLE_MEDIA, + INPUT_GAMING, + INPUT_KEYBOARD, + INPUT_MOUSE, + MEDIA_CDROM, + MEDIA_FLOPPY, + MULTIMEDIA_PLAYER, + NETWORK_WIRED, + NETWORK_WIRELESS, + PRINTER, + EMBLEM_DEFAULT, + EMBLEM_DOCUMENTS, + EMBLEM_DOWNLOADS, + EMBLEM_FAVORITE, + EMBLEM_IMPORTANT, + EMBLEM_MAIL, + EMBLEM_PHOTOS, + EMBLEM_READONLY, + EMBLEM_SHARED, + EMBLEM_SYMBOLIC_LINK, + EMBLEM_SYNCHRONIZED, + EMBLEM_SYSTEM, + EMBLEM_UNREADABLE, + FACE_ANGEL, + FACE_CRYING, + FACE_DEVIL_GRIN, + FACE_DEVIL_SAD, + FACE_GLASSES, + FACE_KISS, + FACE_MONKEY, + FACE_PLAIN, + FACE_SAD, + FACE_SMILE, + FACE_SMILE_BIG, + FACE_SMIRK, + FACE_SURPRISE, + FACE_WINK, + APPLICATION_X_EXECUTABLE, + AUDIO_X_GENERIC, + FONT_X_GENERIC, + IMAGE_X_GENERIC, + PACKAGE_X_GENERIC, + TEXT_HTML, + TEXT_X_GENERIC, + TEXT_X_GENERIC_TEMPLATE, + TEXT_X_SCRIPT, + VIDEO_X_GENERIC, + X_OFFICE_ADDRESS_BOOK, + X_OFFICE_CALENDAR, + X_OFFICE_DOCUMENT, + X_OFFICE_PRESENTATION, + X_OFFICE_SPREADSHEET, + FOLDER, + FOLDER_REMOTE, + NETWORK_SERVER, + NETWORK_WORKGROUP, + START_HERE, + USER_DESKTOP, + USER_HOME, + USER_TRASH, + APPOINTMENT_MISSED, + APPOINTMENT_SOON, + AUDIO_VOLUME_HIGH, + AUDIO_VOLUME_LOW, + AUDIO_VOLUME_MEDIUM, + AUDIO_VOLUME_MUTED, + BATTERY_CAUTION, + BATTERY_LOW, + DIALOG_ERROR, + DIALOG_INFORMATION, + DIALOG_PASSWORD, + DIALOG_QUESTION, + DIALOG_WARNING, + FOLDER_DRAG_ACCEPT, + FOLDER_OPEN, + FOLDER_VISITING, + IMAGE_LOADING, + IMAGE_MISSING, + MAIL_ATTACHMENT, + MAIL_UNREAD, + MAIL_READ, + MAIL_REPLIED, + MAIL_SIGNED, + MAIL_SIGNED_VERIFIED, + MEDIA_PLAYLIST_REPEAT, + MEDIA_PLAYLIST_SHUFFLE, + NETWORK_ERROR, + NETWORK_IDLE, + NETWORK_OFFLINE, + NETWORK_RECEIVE, + NETWORK_TRANSMIT, + NETWORK_TRANSMIT_RECEIVE, + PRINTER_ERROR, + PRINTER_PRINTING, + SOFTWARE_UPDATE_AVAILABLE, + SOFTWARE_UPDATE_URGENT, + SYNC_ERROR, + SYNC_SYNCHRONIZING, + TASK_DUE, + TASK_PASSED_DUE, + USER_AWAY, + USER_IDLE, + USER_OFFLINE, + USER_ONLINE, + USER_TRASH_FULL, + WEATHER_CLEAR, + WEATHER_CLEAR_NIGHT, + WEATHER_FEW_CLOUDS, + WEATHER_FEW_CLOUDS_NIGHT, + WEATHER_FOG, + WEATHER_OVERCAST, + WEATHER_SEVERE_ALERT, + WEATHER_SHOWERS, + WEATHER_SHOWERS_SCATTERED, + WEATHER_SNOW, + WEATHER_STORM, + NULL + }; + +#endif diff --git a/src/tests/compare/efreet_alloc.c b/src/tests/compare/efreet_alloc.c new file mode 100644 index 0000000..1527f77 --- /dev/null +++ b/src/tests/compare/efreet_alloc.c @@ -0,0 +1,30 @@ +#include +#include +#include +#include "comp.h" + +int +main(void) +{ + int i = 0, k, errs = 0; + const char *path; + + efreet_init(); + + for (k = 0; k < LOOPS; k++) + { + for (i = 0; icons[i]; i++) + { + path = efreet_icon_path_find(THEME, icons[i], SIZE); + if (!path) + { + printf("%s: NOT FOUND\n", icons[i]); + errs++; + } + } + } + + efreet_shutdown(); + + return errs > 0; +} diff --git a/src/tests/compare/efreet_menu_alloc.c b/src/tests/compare/efreet_menu_alloc.c new file mode 100644 index 0000000..fcfb75b --- /dev/null +++ b/src/tests/compare/efreet_menu_alloc.c @@ -0,0 +1,23 @@ +#include +#include +#include "comp.h" + +int +main(void) +{ + int k; + + efreet_init(); + + for (k = 0; k < LOOPS; k++) + { + Efreet_Menu *menu; + menu = efreet_menu_get(); + efreet_menu_free(menu); + } + + efreet_shutdown(); + + return 0; +} + diff --git a/src/tests/data/Makefile.am b/src/tests/data/Makefile.am new file mode 100644 index 0000000..50eef85 --- /dev/null +++ b/src/tests/data/Makefile.am @@ -0,0 +1,18 @@ +SUBDIRS = sub + +MAINTAINERCLEANFILES = Makefile.in + +testdir = $(pkgdatadir)/test +test_DATA = \ +test.ini \ +long.ini \ +test.desktop \ +test_type.desktop \ +test.menu \ +test_menu_slash_bad.menu \ +entry.png \ +entry \ +preferences.menu \ +test_garbage + +EXTRA_DIST = $(test_DATA) diff --git a/src/tests/data/entry b/src/tests/data/entry new file mode 100644 index 0000000000000000000000000000000000000000..503d8e5ae500bd59ffec58d5770ec8c45b6291a5 GIT binary patch literal 647 zcmV;20(kw2P)sw;000JJOGiWi{{a60 z|De66lK=n!32;bRa{vGf6951U69E94oEQKA00(qQO+^RQ1`7}nDqA@%Y5)KN6-h)v zRA}Dqn7@wNKoEz&wa0%DmJ9_K3DZLxkW%mQ94>IQ&^TFxga2HZ;Hb! z?!@J|B0;|FPa17AtCjZi%>34By9Vut6 zX_|(zEZOh(Y&IKav)M-h$g+$e2pEkHm$7l8T-1J+_*k!c)g!9kurfI0Ein^|;s>)b& z`@HJr^QvWeR%0rwUfCTzD`6L#Ld5A&8J3DfBm;G2YDSj^}1&x^(4(>c$z hSM@H`4(RW|*WbN#AC5tq<>UYW002ovPDHLkV1j9_FrWYc literal 0 HcmV?d00001 diff --git a/src/tests/data/entry.png b/src/tests/data/entry.png new file mode 100644 index 0000000000000000000000000000000000000000..82e5cbe4975031561c2da213a18d37dca4a5fa21 GIT binary patch literal 648 zcmV;30(bq1P)W-o00006VoOIv0RI60 z0RN!9r;`8x010qNS#tmY3ljhU3ljkVnw%H_000McNliru)dmX?5Gq?aEouM&0u@O_ zK~!ko?U=ug+CUJ8zqQAI5S9!D7YWlt8<0})AiPqFlsr$KAyGP7EK^vPBDo+SY;TIg zEAGVQxFSKm>`xkPGpm*M^UVC#YQ0{603Lt=k5bQ8t8PhOuA|ZD*J`zj1$bC4mp{@p zWw+a*wMOg5+;+9k&l}Q4|q| zp{ZyKx?7;Ds>1WUC)}h>5D`4jBM1V*FtngM4jSNeIvGQ4D%x=zL*0%2ZwC?BkevP4A&ev=47RP7x_uTYb%-ChL`-Jn)@1|*}s*1X12O_b`*47*W`IlmSrSKVnF|#OeSo%TN8HPz7O-3Bni{$6yTeLVOY%P^UsUL;?p_L iwO92n)DGzHz}MfsbRUjEn&spG0000 + + + Preferences + Preferences.directory + + blah + /var/tmp + + + BlahBorp + + + + House + House.directory + + House + Garden + + + + Mouse + House.directory + + House + Garden + + + + House + House.directory + + Cat + + + + diff --git a/src/tests/data/sub/Makefile.am b/src/tests/data/sub/Makefile.am new file mode 100644 index 0000000..7aaf7fc --- /dev/null +++ b/src/tests/data/sub/Makefile.am @@ -0,0 +1,8 @@ + +MAINTAINERCLEANFILES = Makefile.in + +testdir = $(pkgdatadir)/test/sub +test_DATA = \ +test.desktop + +EXTRA_DIST = $(test_DATA) diff --git a/src/tests/data/sub/test.desktop b/src/tests/data/sub/test.desktop new file mode 100644 index 0000000..7abf2ae --- /dev/null +++ b/src/tests/data/sub/test.desktop @@ -0,0 +1,5 @@ +[Desktop Entry] +Type=Application +Name=Sub +Exec=subtest +Categories=Test diff --git a/src/tests/data/test.desktop b/src/tests/data/test.desktop new file mode 100644 index 0000000..412601f --- /dev/null +++ b/src/tests/data/test.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Type=Application +Name=Efreet Test Application +GenericName=Test Application +Exec=efreet_test %F %i +Categories=Test;Enlightenment; +Icon=TestIcon +X-Test=Own key diff --git a/src/tests/data/test.ini b/src/tests/data/test.ini new file mode 100644 index 0000000..7f73d81 --- /dev/null +++ b/src/tests/data/test.ini @@ -0,0 +1,21 @@ +# Comments should be ignored (and empty lines) + +[contact] +Name=Foo Bar +Name[en_US]=English Foo Bar +Email= foo@bar.com +Email[de_DE] = foo@bar.de +Age = 30 +TrueBoolean=true +FalseBoolean=false +InvalidBoolean=invalid +Escaped=line1\nline2\r\nline3\ttabbed \\ with a backslash\sand\sspaces + +[AIM] +Username=foobar + +# the next line has a single space. it should be skipped as well + +[Jabber] +Username=foobar@bar.de + diff --git a/src/tests/data/test.menu b/src/tests/data/test.menu new file mode 100644 index 0000000..7ae21ff --- /dev/null +++ b/src/tests/data/test.menu @@ -0,0 +1,52 @@ + + + + Applications + Applications.directory + + + + + applications-merged + + /usr/share/applnk + + + + + + More + + + + Foo + Bar + Foo2 + Bar2 + + + + Preferences + Preferences.directory + preferences.menu + + + + Office + Office.directory + + Office + + + foo.desktop + + + bar.desktop + + + + + + diff --git a/src/tests/data/test_garbage b/src/tests/data/test_garbage new file mode 100644 index 0000000..7d178fd --- /dev/null +++ b/src/tests/data/test_garbage @@ -0,0 +1,2341 @@ +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcdef +abcd \ No newline at end of file diff --git a/src/tests/data/test_menu_bad_comment.menu b/src/tests/data/test_menu_bad_comment.menu new file mode 100644 index 0000000..5815b89 --- /dev/null +++ b/src/tests/data/test_menu_bad_comment.menu @@ -0,0 +1,14 @@ + + + + Applications + Applications.directory + + + This should be commented + --> + + diff --git a/src/tests/data/test_menu_slash_bad.menu b/src/tests/data/test_menu_slash_bad.menu new file mode 100644 index 0000000..8f7b6f0 --- /dev/null +++ b/src/tests/data/test_menu_slash_bad.menu @@ -0,0 +1,11 @@ + + + + Applications/Bar + + + Preferences/Baz + + diff --git a/src/tests/data/test_type.desktop b/src/tests/data/test_type.desktop new file mode 100644 index 0000000..1b67ff5 --- /dev/null +++ b/src/tests/data/test_type.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Type=My_Type +Name=Efreet Test Application +GenericName=Test Application +Exec=efreet_test %F %i +Categories=Test;Enlightenment; +Icon=TestIcon +X-Test=Own key diff --git a/src/tests/ef_cache.c b/src/tests/ef_cache.c new file mode 100644 index 0000000..3cbee9e --- /dev/null +++ b/src/tests/ef_cache.c @@ -0,0 +1,199 @@ +#include "Efreet.h" +#include +#include +#include "ef_test.h" + +#if 0 +EAPI Efreet_Desktop *efreet_util_desktop_file_id_find(const char *file_id); + +EAPI Eina_List *efreet_util_desktop_generic_name_glob_list(const char *glob); +EAPI Eina_List *efreet_util_desktop_comment_glob_list(const char *glob); +#endif + +static Eina_Bool icon_cb = EINA_FALSE; +static Eina_Bool desktop_cb = EINA_FALSE; + +static void +check(void) +{ + Eina_List *list; + Efreet_Desktop *desktop, *desktop2; + double start; + const char *id; + + // EAPI char *efreet_util_path_to_file_id(const char *path); + start = ecore_time_get(); + id = efreet_util_path_to_file_id("/usr/share/applications/gnome-panel.desktop"); + if (id) + { + printf("efreet_util_path_to_file_id(/usr/share/applications/gnome-panel.desktop): %s %.6f\n", id, (ecore_time_get() - start)); + } + else + printf("efreet_util_path_to_file_id(/usr/share/applications/gnome-panel.desktop): NULL %.6f\n", (ecore_time_get() - start)); + + //EAPI Efreet_Desktop *efreet_util_desktop_name_find(const char *name); + start = ecore_time_get(); + desktop = efreet_util_desktop_name_find("Evolution"); + if (desktop) + printf("efreet_util_desktop_name_find(Evolution): %s %.6f\n", desktop->orig_path, (ecore_time_get() - start)); + else + printf("efreet_util_desktop_name_find(Evolution): NULL %.6f\n", (ecore_time_get() - start)); + efreet_desktop_free(desktop); + + //EAPI Efreet_Desktop *efreet_util_desktop_generic_name_find(const char *generic_name); + start = ecore_time_get(); + desktop = efreet_util_desktop_generic_name_find("Spreadsheet"); + if (desktop) + printf("efreet_util_desktop_generic_name_find(Spreadsheet): %s %.6f\n", desktop->orig_path, (ecore_time_get() - start)); + else + printf("efreet_util_desktop_generic_name_find(Spreadsheet): NULL %.6f\n", (ecore_time_get() - start)); + efreet_desktop_free(desktop); + + //EAPI Efreet_Desktop *efreet_util_desktop_wm_class_find(const char *wmname, const char *wmclass); + start = ecore_time_get(); + desktop = efreet_util_desktop_wm_class_find("Firefox", NULL); + if (desktop) + printf("efreet_util_desktop_wm_class_find(Firefox): %s %.6f\n", desktop->orig_path, (ecore_time_get() - start)); + else + printf("efreet_util_desktop_wm_class_find(Firefox): NULL %.6f\n", (ecore_time_get() - start)); + efreet_desktop_free(desktop); + + //EAPI Efreet_Desktop *efreet_util_desktop_exec_find(const char *exec); + start = ecore_time_get(); + desktop = efreet_util_desktop_exec_find("/usr/bin/update-manager"); + if (desktop) + printf("efreet_util_desktop_exec_find(update-manager): %s %.6f\n", desktop->orig_path, (ecore_time_get() - start)); + else + printf("efreet_util_desktop_exec_find(update-manager): NULL %.6f\n", (ecore_time_get() - start)); + efreet_desktop_free(desktop); + + //EAPI Eina_List *efreet_util_desktop_name_glob_list(const char *glob); + start = ecore_time_get(); + list = efreet_util_desktop_name_glob_list("Ubuntu*"); + if (list) + { + EINA_LIST_FREE(list, desktop) + { + printf("efreet_util_desktop_name_glob_list(Ubuntu*): %s\n", desktop->name); + efreet_desktop_free(desktop); + } + } + printf("time: %.6f\n", (ecore_time_get() - start)); + + //EAPI Eina_List *efreet_util_desktop_mime_list(const char *mime); + start = ecore_time_get(); + list = efreet_util_desktop_mime_list("application/ogg"); + if (list) + { + EINA_LIST_FREE(list, desktop) + { + printf("efreet_util_desktop_mime_list(application/ogg): %s\n", desktop->name); + efreet_desktop_free(desktop); + } + } + printf("time: %.6f\n", (ecore_time_get() - start)); + + //EAPI Eina_List *efreet_util_desktop_exec_glob_list(const char *glob); + start = ecore_time_get(); + list = efreet_util_desktop_exec_glob_list("*gnome*"); + if (list) + { + EINA_LIST_FREE(list, desktop) + { + printf("efreet_util_desktop_exec_glob_list(*gnome*): %s\n", desktop->exec); + efreet_desktop_free(desktop); + } + } + printf("time: %.6f\n", (ecore_time_get() - start)); + + //EAPI Eina_List *efreet_util_desktop_categories_list(void); + start = ecore_time_get(); + list = efreet_util_desktop_categories_list(); + if (list) + { + EINA_LIST_FREE(list, id) + { + printf("efreet_util_desktop_categories_list(): %s\n", id); + } + } + printf("time: %.6f\n", (ecore_time_get() - start)); + + //EAPI Eina_List *efreet_util_desktop_category_list(const char *category); + start = ecore_time_get(); + list = efreet_util_desktop_category_list("Graphics"); + if (list) + { + EINA_LIST_FREE(list, desktop) + { + printf("efreet_util_desktop_category_list(Graphics): %s\n", desktop->name); + efreet_desktop_free(desktop); + } + } + printf("time: %.6f\n", (ecore_time_get() - start)); + + desktop = efreet_desktop_get("/opt/google/chrome/google-chrome.desktop"); + if (desktop) + printf("%s: %d %d\n", desktop->orig_path, desktop->ref, desktop->eet); + desktop2 = efreet_desktop_new("/opt/google/chrome/google-chrome.desktop"); + if (desktop2) + { + printf("%s: %d %d\n", desktop2->orig_path, desktop2->ref, desktop2->eet); + efreet_desktop_free(desktop2); + } + if (desktop) + efreet_desktop_free(desktop); + + desktop = efreet_desktop_get("/usr/share/applications/firefox.desktop"); + if (desktop) + printf("%s: %d %d\n", desktop->orig_path, desktop->ref, desktop->eet); + desktop2 = efreet_desktop_new("/usr/share/applications/firefox.desktop"); + if (desktop2) + { + printf("%s: %d %d\n", desktop2->orig_path, desktop2->ref, desktop2->eet); + efreet_desktop_free(desktop2); + } + if (desktop) + efreet_desktop_free(desktop); + fflush(stdout); +} + +static Eina_Bool +icon_handler_cb(void *data __UNUSED__, int event_type __UNUSED__, void *event __UNUSED__) +{ + icon_cb = EINA_TRUE; + if (icon_cb && desktop_cb) + { + check(); + ecore_main_loop_quit(); + } + return ECORE_CALLBACK_PASS_ON; +} + +static Eina_Bool +desktop_handler_cb(void *data __UNUSED__, int event_type __UNUSED__, void *event __UNUSED__) +{ + desktop_cb = EINA_TRUE; + if (icon_cb && desktop_cb) + { + check(); + ecore_main_loop_quit(); + } + return ECORE_CALLBACK_PASS_ON; +} + +int +main(int argc __UNUSED__, char **argv __UNUSED__) +{ + Ecore_Event_Handler *icon_handler; + Ecore_Event_Handler *desktop_handler; + + if (!efreet_init()) return 1; + icon_handler = ecore_event_handler_add(EFREET_EVENT_ICON_CACHE_UPDATE, icon_handler_cb, NULL); + desktop_handler = ecore_event_handler_add(EFREET_EVENT_DESKTOP_CACHE_UPDATE, desktop_handler_cb, NULL); + check(); + ecore_main_loop_begin(); + ecore_event_handler_del(icon_handler); + ecore_event_handler_del(desktop_handler); + efreet_shutdown(); + return 0; +} diff --git a/src/tests/ef_data_dirs.c b/src/tests/ef_data_dirs.c new file mode 100644 index 0000000..a99b2ae --- /dev/null +++ b/src/tests/ef_data_dirs.c @@ -0,0 +1,330 @@ +#include "Efreet.h" +#include +#include +#include +#include + +int +ef_cb_efreet_data_home(void) +{ + const char *tmp; + int ret = 1; + + efreet_shutdown(); + setenv("XDG_DATA_HOME", "/var/tmp", 1); + efreet_init(); + + tmp = efreet_data_home_get(); + if (strcmp(tmp, "/var/tmp")) + { + printf("efreet_data_home_get() returned incorrect " + "value (%s) on XDG_DATA_HOME=/var/tmp\n", tmp); + ret = 0; + } + + /* reset efreet here so we can set a new home dir */ + efreet_shutdown(); + unsetenv("XDG_DATA_HOME"); + setenv("HOME", "/home/tmp", 1); + efreet_init(); + + tmp = efreet_data_home_get(); + if (strcmp(tmp, "/home/tmp/.local/share")) + { + printf("efreet_data_home_get() returned incorrect " + "value (%s) on blank XDG_DATA_HOME\n", tmp); + ret = 0; + } + + /* reset efreet here so we can set a new home dir */ + efreet_shutdown(); + unsetenv("XDG_DATA_HOME"); + unsetenv("HOME"); +#ifdef _WIN32 + unsetenv("USERPROFILE"); +#endif + efreet_init(); + + tmp = efreet_data_home_get(); + if (strcmp(tmp, "/tmp/.local/share")) + { + printf("efreet_data_home_get() returned incorrect " + "value (%s) on blank XDG_DATA_HOME and blank HOME\n", tmp); + ret = 0; + } + + return ret; +} + +int +ef_cb_efreet_config_home(void) +{ + const char *tmp; + int ret = 1; + + efreet_shutdown(); + setenv("XDG_CONFIG_HOME", "/var/tmp", 1); + efreet_init(); + + tmp = efreet_config_home_get(); + if (strcmp(tmp, "/var/tmp")) + { + printf("efreet_config_home_get() returned incorrect " + "value (%s) on XDG_CONFIG_HOME=/var/tmp\n", tmp); + ret = 0; + } + + /* reset efreet here so we can set a new home dir */ + efreet_shutdown(); + unsetenv("XDG_CONFIG_HOME"); + setenv("HOME", "/home/tmp", 1); + efreet_init(); + + tmp = efreet_config_home_get(); + if (strcmp(tmp, "/home/tmp/.config")) + { + printf("efreet_config_home_get() returned incorrect " + "value (%s) on blank XDG_CONFIG_HOME\n", tmp); + ret = 0; + } + + /* reset efreet here so we can set a new home dir */ + efreet_shutdown(); + unsetenv("XDG_CONFIG_HOME"); + unsetenv("HOME"); +#ifdef _WIN32 + unsetenv("USERPROFILE"); +#endif + efreet_init(); + + tmp = efreet_config_home_get(); + if (strcmp(tmp, "/tmp/.config")) + { + printf("efreet_config_home_get() returned incorrect " + "value (%s) on blank XDG_CONFIG_HOME and blank HOME\n", tmp); + ret = 0; + } + + return ret; +} + +int +ef_cb_efreet_cache_home(void) +{ + const char *tmp; + int ret = 1; + + efreet_shutdown(); + setenv("XDG_CACHE_HOME", "/var/tmp", 1); + efreet_init(); + + tmp = efreet_cache_home_get(); + if (strcmp(tmp, "/var/tmp")) + { + printf("efreet_cache_home_get() returned incorrect " + "value (%s) on XDG_CACHE_HOME=/var/tmp\n", tmp); + ret = 0; + } + + /* reset efreet here so we can set a new home dir */ + efreet_shutdown(); + unsetenv("XDG_CACHE_HOME"); + setenv("HOME", "/home/tmp", 1); + efreet_init(); + + tmp = efreet_cache_home_get(); + if (strcmp(tmp, "/home/tmp/.cache")) + { + printf("efreet_cache_home_get() returned incorrect " + "value (%s) on blank XDG_CACHE_HOME\n", tmp); + ret = 0; + } + + /* reset efreet here so we can set a new home dir */ + efreet_shutdown(); + unsetenv("XDG_CACHE_HOME"); + unsetenv("HOME"); +#ifdef _WIN32 + unsetenv("USERPROFILE"); +#endif + efreet_init(); + + tmp = efreet_cache_home_get(); + if (strcmp(tmp, "/tmp/.cache")) + { + printf("efreet_cache_home_get() returned incorrect " + "value (%s) on blank XDG_CACHE_HOME and blank HOME\n", tmp); + ret = 0; + } + + return ret; +} + +int +ef_cb_efreet_data_dirs(void) +{ + Eina_List *tmp, *l; + int ret = 1; + unsigned int i; + unsigned int ok; + char dirs[128], *val; + char *vals[] = {"/var/tmp/a", "/tmp/b", "/usr/local/share", "/etc", NULL}; + char *def_vals[] = {PACKAGE_DATA_DIR, "/usr/share", "/usr/local/share", NULL}; + + dirs[0] = '\0'; + for (i = 0; vals[i]; i++) + { + if (i > 0) strcat(dirs, ":"); + strcat(dirs, vals[i]); + } + + efreet_shutdown(); + setenv("XDG_DATA_DIRS", dirs, 1); + efreet_init(); + + ok = 0; + tmp = efreet_data_dirs_get(); + for (i = 0; vals[i]; i++) + { + char *found; + + found = eina_list_search_unsorted(tmp, EINA_COMPARE_CB(strcmp), vals[i]); + if (!ecore_file_exists(vals[i]) && found) + { + printf("efreet_data_dirs_get() includes non-existing dir (%s) when " + "%s set\n", vals[i], dirs); + ret = 0; + continue; + } + if (ecore_file_exists(vals[i]) && !found) + { + printf("efreet_data_dirs_get() is missing dir (%s) when " + "%s set\n", vals[i], dirs); + ret = 0; + continue; + } + if (ecore_file_exists(vals[i]) && found) + ok++; + } + if (eina_list_count(tmp) != ok) + { + printf("efreet_data_dirs_get() returned more values then it " + "should have given %s as input\n", dirs); + ret = 0; + } + + efreet_shutdown(); + unsetenv("XDG_DATA_DIRS"); + efreet_init(); + + i = 0; + tmp = efreet_data_dirs_get(); + if (eina_list_count(tmp) != 3) + { + printf("efreet_data_dirs_get() nodes is differnet from expected default\n"); + ret = 0; + } + + EINA_LIST_FOREACH(tmp, l, val) + { + if (!def_vals[i]) + { + printf("efreet_data_dirs_get() returned more values then it " + "should have given %s as input\n", dirs); + ret = 0; + break; + } + + if (strcmp(val, def_vals[i])) + { + printf("efreet_data_dirs_get() returned incorrect value (%s) when " + "XDG_DATA_DIRS= is set %s\n", val, def_vals[i]); + ret = 0; + } + + i++; + } + return ret; +} + +int +ef_cb_efreet_config_dirs(void) +{ + Eina_List *tmp, *l; + int ret = 1; + unsigned int i; + unsigned int ok; + char dirs[128], *val; + char *vals[] = {"/var/tmp/a", "/tmp/b", "/usr/local/share", "/etc", NULL}; + char *def_vals[] = {"/etc/xdg", NULL}; + + dirs[0] = '\0'; + + for (i = 0; vals[i]; i++) + { + if (i > 0) strcat(dirs, ":"); + strcat(dirs, vals[i]); + } + + efreet_shutdown(); + setenv("XDG_CONFIG_DIRS", dirs, 1); + efreet_init(); + + ok = 0; + tmp = efreet_config_dirs_get(); + for (i = 0; vals[i]; i++) + { + char *found; + + found = eina_list_search_unsorted(tmp, EINA_COMPARE_CB(strcmp), vals[i]); + if (!ecore_file_exists(vals[i]) && found) + { + printf("efreet_data_dirs_get() includes non-existing dir (%s) when " + "%s set\n", vals[i], dirs); + ret = 0; + continue; + } + if (ecore_file_exists(vals[i]) && !found) + { + printf("efreet_data_dirs_get() is missing dir (%s) when " + "%s set\n", vals[i], dirs); + ret = 0; + continue; + } + if (ecore_file_exists(vals[i]) && found) + ok++; + } + if (eina_list_count(tmp) != ok) + { + printf("efreet_data_dirs_get() returned more values then it " + "should have given %s as input\n", dirs); + ret = 0; + } + + efreet_shutdown(); + unsetenv("XDG_CONFIG_DIRS"); + efreet_init(); + + i = 0; + tmp = efreet_config_dirs_get(); + EINA_LIST_FOREACH(tmp, l, val) + { + if (!def_vals[i]) + { + printf("efreet_config_dirs_get() returned more values then it " + "should have given %s as input\n", dirs); + ret = 0; + break; + } + + if (strcmp(val, def_vals[i])) + { + printf("efreet_config_dirs_get() returned incorrect value (%s) when " + "XDG_CONFIG_DIRS= is set\n", val); + ret = 0; + } + + i++; + } + return ret; +} diff --git a/src/tests/ef_desktop.c b/src/tests/ef_desktop.c new file mode 100644 index 0000000..39f942a --- /dev/null +++ b/src/tests/ef_desktop.c @@ -0,0 +1,401 @@ +#include "Efreet.h" +#include "config.h" +#include +#include +#include +#include +#include +#include "ef_test.h" + +static void *_cb_command(void *data, Efreet_Desktop *desktop, char *exec, int remaining); + + +int +ef_cb_desktop_parse(void) +{ + Efreet_Desktop *desktop; + Eina_List *l; + int ret = 1; + + desktop = efreet_desktop_get(PKG_DATA_DIR"/test/test.desktop"); + if (!desktop) + { + printf("No desktop found.\n"); + return 0; + } + + if (!desktop->name || strcmp(desktop->name, "Efreet Test Application")) + { + printf("Invalid Name\n"); + ret = 0; + } + + if (!desktop->generic_name || + strcmp(desktop->generic_name, "Test Application")) + { + printf("Incorrent GenericName\n"); + ret = 0; + } + + if (!desktop->exec || strcmp(desktop->exec, "efreet_test %F %i")) + { + printf("Incorrect Exec (%s)\n", (desktop->exec ? desktop->exec : "(null)")); + ret = 0; + } + + if (desktop->categories) + { + const char *categories[] = {"Test", "Enlightenment"}; + const char *cat; + int num_categories = 2, i = 0; + + EINA_LIST_FOREACH(desktop->categories, l, cat) + { + if (i >= num_categories) + { + printf("Too many categories found.\n"); + ret = 0; + break; + } + + if (!cat || !categories[i] || strcmp(cat, categories[i])) + { + printf("Expected category %s, found %s\n", categories[i], cat); + ret = 0; + } + i++; + } + } + else ret = 0; + + efreet_desktop_free(desktop); + + return ret; +} + +#if 0 +int +ef_cb_desktop_file_id(void) +{ + Efreet_Desktop *desktop; + int ret = 1; + + desktop = efreet_desktop_get(PKG_DATA_DIR"/test/test.desktop"); + if (desktop) + { + const char *id; + int i = 0; + + struct { + char *dir; + int legacy; + char *prefix; + char *expected; + } tests[] = { + {PKG_DATA_DIR"/test/", 0, NULL, "test.desktop"}, + {PKG_DATA_DIR"/", 0, NULL, "test-test.desktop"}, + {PKG_DATA_DIR"/", 1, NULL, "test.desktop"}, + {PKG_DATA_DIR"/", 1, "prefix", "prefix-test.desktop"}, + {NULL, 0, NULL, NULL} + }; + + for (i = 0; tests[i].dir != NULL; i++) + { + id = efreet_desktop_id_get(desktop, + tests[i].dir, + tests[i].legacy, + tests[i].prefix); + if (!id || strcmp(id, tests[i].expected)) + { + printf("Expecting id: %s, got: %s\n", tests[i].expected, id); + ret = 0; + } + if (id) eina_stringshare_del(id); + } + } + else + ret = 0; + + return ret; +} +#endif + +int +ef_cb_desktop_save(void) +{ + Efreet_Desktop *desktop; + + printf("\n"); + desktop = efreet_desktop_get(PKG_DATA_DIR"/test/test.desktop"); + if (!desktop) + { + printf("Failed to get Desktop file\n"); + return 0; + } + + printf("save data: %d\n", efreet_desktop_save(desktop)); + efreet_desktop_free(desktop); + + desktop = efreet_desktop_empty_new("/tmp/test.desktop"); + desktop->name = strdup("Efreet Test Application"); + desktop->type = EFREET_DESKTOP_TYPE_APPLICATION; + desktop->generic_name = strdup("Test Application"); + desktop->exec = strdup("efreet_test"); + desktop->categories = NULL; + desktop->categories = eina_list_append(desktop->categories, eina_stringshare_add("Test")); + desktop->categories = eina_list_append(desktop->categories, eina_stringshare_add("Enlightenment")); + printf("save test: %d\n", efreet_desktop_save(desktop)); + unlink("/tmp/test.desktop"); + efreet_desktop_free(desktop); + + return 1; +} + +typedef struct +{ + Eina_List *expected; + int error; + char type; +} Test_Info; + +int +ef_cb_desktop_command_get(void) +{ + Efreet_Desktop *desktop; + Eina_List *files, *expected; + char olddir[PATH_MAX]; + Test_Info *info; + int ret; + + if (getcwd(olddir, PATH_MAX) != 0) ret = 0; + if (chdir("/") != 0) ret = 0; + + printf("\n"); + desktop = efreet_desktop_empty_new("test.desktop"); + + desktop->name = strdup("App Name"); + desktop->icon = strdup("icon.png"); + + files = NULL; + files = eina_list_append(files, "/tmp/absolute_path"); + files = eina_list_append(files, "relative_path"); + files = eina_list_append(files, "file:///tmp/absolute_uri"); + files = eina_list_append(files, "file:relative_uri"); + + info = NEW(Test_Info, 1); + expected = NULL; + info->error = 0; + + /* test single full path */ + info->type = 'f'; + IF_FREE(desktop->exec); + desktop->exec = strdup("app %f"); + expected = eina_list_append(expected, "app '/tmp/absolute_path'"); + expected = eina_list_append(expected, "app '/relative_path'"); + expected = eina_list_append(expected, "app '/tmp/absolute_uri'"); + expected = eina_list_append(expected, "app '/relative_uri'"); + + info->expected = expected; + efreet_desktop_command_get(desktop, files, _cb_command, info); + expected = eina_list_free(expected); + + /* test single uri */ + info->type = 'u'; + IF_FREE(desktop->exec); + desktop->exec = strdup("app %u"); + expected = eina_list_append(expected, "app 'file:///tmp/absolute_path'"); + expected = eina_list_append(expected, "app 'file:///relative_path'"); + expected = eina_list_append(expected, "app 'file:///tmp/absolute_uri'"); + expected = eina_list_append(expected, "app 'file:///relative_uri'"); + + info->expected = expected; + efreet_desktop_command_get(desktop, files, _cb_command, info); + expected = eina_list_free(expected); + + /* test single dir */ +#if 0 + info->type = 'd'; + IF_FREE(desktop->exec); + desktop->exec = strdup("app %d"); + expected = eina_list_append(expected, "app '/tmp'"); + expected = eina_list_append(expected, "app '/'"); + expected = eina_list_append(expected, "app '/tmp'"); + expected = eina_list_append(expected, "app '/'"); + + info->expected = expected; + efreet_desktop_command_get(desktop, files, _cb_command, info); + expected = eina_list_free(expected); +#endif + + + /* test single names */ +#if 0 + info->type = 'n'; + IF_FREE(desktop->exec); + desktop->exec = strdup("app %n"); + expected = eina_list_append(expected, "app 'absolute_path'"); + expected = eina_list_append(expected, "app 'relative_path'"); + expected = eina_list_append(expected, "app 'absolute_uri'"); + expected = eina_list_append(expected, "app 'relative_uri'"); + + info->expected = expected; + efreet_desktop_command_get(desktop, files, _cb_command, info); + expected = eina_list_free(expected); +#endif + + /* test multiple fullpaths */ + info->type = 'F'; + IF_FREE(desktop->exec); + desktop->exec = strdup("app %F"); + expected = eina_list_append(expected, "app '/tmp/absolute_path' '/relative_path' '/tmp/absolute_uri' '/relative_uri'"); + + info->expected = expected; + efreet_desktop_command_get(desktop, files, _cb_command, info); + expected = eina_list_free(expected); + + /* test multiple URIs */ + info->type = 'U'; + IF_FREE(desktop->exec); + desktop->exec = strdup("app %U"); + expected = eina_list_append(expected, "app 'file:///tmp/absolute_path' 'file:///relative_path' 'file:///tmp/absolute_uri' 'file:///relative_uri'"); + + info->expected = expected; + efreet_desktop_command_get(desktop, files, _cb_command, info); + expected = eina_list_free(expected); + + /* test multiple dirs */ +#if 0 + info->type = 'D'; + IF_FREE(desktop->exec); + desktop->exec = strdup("app %D"); + expected = eina_list_append(expected, "app '/tmp' '/' '/tmp' '/'"); + + info->expected = expected; + efreet_desktop_command_get(desktop, files, _cb_command, info); + expected = eina_list_free(expected); +#endif + + /* test multiple names */ +#if 0 + info->type = 'N'; + IF_FREE(desktop->exec); + desktop->exec = strdup("app %N"); + expected = eina_list_append(expected, "app 'absolute_path' 'relative_path' 'absolute_uri' 'relative_uri'"); + + info->expected = expected; + efreet_desktop_command_get(desktop, files, _cb_command, info); + expected = eina_list_free(expected); +#endif + + /* test icon appending */ + info->type = 'i'; + IF_FREE(desktop->exec); + desktop->exec = strdup("app %i"); + expected = eina_list_append(expected, "app --icon 'icon.png'"); + + info->expected = expected; + efreet_desktop_command_get(desktop, NULL, _cb_command, info); + expected = eina_list_free(expected); + + /* test app name */ + info->type = 'c'; + IF_FREE(desktop->exec); + desktop->exec = strdup("app %c"); + expected = eina_list_append(expected, "app 'App Name'"); + + info->expected = expected; + efreet_desktop_command_get(desktop, NULL, _cb_command, info); + expected = eina_list_free(expected); + + /* test desktop path */ + info->type = 'k'; + IF_FREE(desktop->exec); + desktop->exec = strdup("app %k"); + expected = eina_list_append(expected, "app 'test.desktop'"); + + info->expected = expected; + efreet_desktop_command_get(desktop, NULL, _cb_command, info); + eina_list_free(expected); + + /* clean up */ + efreet_desktop_free(desktop); + eina_list_free(files); + + if (chdir(olddir) != 0) ret = 0; + + ret = info->error > 0 ? 0 : 1; + free(info); + + return ret; +} + +static void * +_cb_command(void *data, Efreet_Desktop *desktop __UNUSED__, + char *exec, int remaining __UNUSED__) +{ + Test_Info *info = data; + char *expected; + + expected = eina_list_data_get(info->expected); + info->expected = eina_list_demote_list(info->expected, info->expected); + if (!expected) + { + printf(" ERROR: (%%%c) got \"%s\", expected nothing\n", info->type, exec); + info->error++; + } + else + { + if (strcmp(exec, expected)) + { + printf(" ERROR: (%%%c) got \"%s\", expected \"%s\"\n", info->type, exec, expected); + info->error++; + } + } + free(exec); + return NULL; +} + +static void * +cb_type_parse(Efreet_Desktop *desktop __UNUSED__, Efreet_Ini *ini) +{ + const char *val; + val = efreet_ini_string_get(ini, "X-Test"); + if (!val) return NULL; + return (void *)strdup(val); +} + +int +ef_cb_desktop_type_parse(void) +{ + Efreet_Desktop *desktop; + int my_type; + char *val; + int ret = 1; + + /* add my custom desktop type to efreet */ + my_type = efreet_desktop_type_add("My_Type", cb_type_parse, NULL, + (Efreet_Desktop_Type_Free_Cb)free); + + desktop = efreet_desktop_get(PKG_DATA_DIR"/test/test_type.desktop"); + if (!desktop) + { + printf("No desktop found.\n"); + return 0; + } + + if (desktop->type != my_type) + { + printf("Invalid type returned in desktop"); + ret = 0; + } + + val = (char *)efreet_desktop_type_data_get(desktop); + if (!val || strcmp(val, "Own key")) + { + printf("Invalid value of custom key (%s).\n", val); + ret = 0; + } + + efreet_desktop_free(desktop); + return ret; +} diff --git a/src/tests/ef_icon_theme.c b/src/tests/ef_icon_theme.c new file mode 100644 index 0000000..1a04cfa --- /dev/null +++ b/src/tests/ef_icon_theme.c @@ -0,0 +1,605 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "Efreet.h" +#include +#include +#include +#include +#include +#include + +#define SIZE 128 +#define THEME "Tango" +#define FREE(x) do { free(x); x = NULL; } while (0); + +static Eina_Bool _hash_keys(Eina_Hash *hash, const char *key, void *list); +static void ef_icon_theme_themes_find(const char *search_dir, + Eina_Hash *themes); +static void ef_icons_find(Efreet_Icon_Theme *theme, Eina_Hash *icons); +static void ef_read_dir(const char *dir, Eina_Hash *icons); + +int +ef_cb_efreet_icon_theme(void) +{ + int ret = 1; + const char *tmp; + + unsetenv("XDG_DATA_HOME"); + efreet_shutdown(); + putenv("HOME=/var/tmp"); + efreet_init(); + + tmp = efreet_icon_user_dir_get(); + if (strcmp(tmp, "/var/tmp/.local/share/icons")) + { + printf("efreet_icon_user_dir_get() returned incorrect " + "value (%s) on HOME=/var/tmp\n", tmp); + ret = 0; + } + + efreet_shutdown(); + unsetenv("HOME"); +#ifdef _WIN32 + unsetenv("USERPROFILE"); +#endif + efreet_init(); + + tmp = efreet_icon_user_dir_get(); + if (strcmp(tmp, "/tmp/.local/share/icons")) + { + printf("efreet_icon_user_dir_get() returned incorrect " + "value (%s) on HOME=\n", tmp); + ret = 0; + } + + return ret; +} + +static Eina_Bool +_hash_keys(Eina_Hash *hash __UNUSED__, const char *key, void *list) +{ + Eina_List **l = list; + + *l = eina_list_append(*l, key); + return EINA_TRUE; +} + +int +ef_cb_efreet_icon_theme_list(void) +{ + int ret = 1; + Eina_List *themes; + Eina_List *icon_dirs; + Eina_List *l; + Eina_Hash *dirs; + Eina_Iterator *it; + Efreet_Icon_Theme *theme; + const char *dir; + char buf[PATH_MAX]; + + dirs = eina_hash_string_superfast_new(free); + + icon_dirs = efreet_data_dirs_get(); + + ef_icon_theme_themes_find(efreet_icon_user_dir_get(), dirs); + EINA_LIST_FOREACH(icon_dirs, l, dir) + { + snprintf(buf, sizeof(buf), "%s/icons", dir); + ef_icon_theme_themes_find(buf, dirs); + } + EINA_LIST_FOREACH(icon_dirs, l, dir) + { + snprintf(buf, sizeof(buf), "%s/pixmaps", dir); + ef_icon_theme_themes_find(buf, dirs); + } + ef_icon_theme_themes_find("/usr/share/pixmaps", dirs); + + themes = efreet_icon_theme_list_get(); + EINA_LIST_FOREACH(themes, l, theme) + { + if ((eina_hash_find(dirs, theme->name.internal))) + eina_hash_del(dirs, theme->name.internal, NULL); + else + { + printf("efreet_icon_theme_list_get() returned %s which we didn't " + "see when scanning the directories.\n", theme->name.internal); + ret = 0; + } + } + while (themes) + { + themes = eina_list_remove_list(themes, themes); + } + + themes = NULL; + it = eina_hash_iterator_key_new(dirs); + eina_iterator_foreach(it, EINA_EACH_CB(_hash_keys), &themes); + eina_iterator_free(it); + + if (eina_list_count(themes) > 0) + { + printf("efreet_icon_theme_list_get() missed: "); + EINA_LIST_FOREACH(themes, l, dir) + printf("%s ", dir); + printf("\n"); + + ret = 0; + } + while (themes) + { + themes = eina_list_remove_list(themes, themes); + } + eina_hash_free(dirs); + + return ret; +} + +static void +ef_icon_theme_themes_find(const char *search_dir, Eina_Hash *themes) +{ + Eina_List *dirs; + char *dir; + + if (!search_dir || !themes) return; + + dirs = ecore_file_ls(search_dir); + if (!dirs) return; + + while ((dir = eina_list_data_get(dirs))) + { + char p[PATH_MAX]; + + dirs = eina_list_remove_list(dirs, dirs); + /* if we've already added the theme we're done */ + if (eina_hash_find(themes, dir)) + { + free(dir); + continue; + } + + /* if the index.theme file exists we open it and look for the hidden + * flag. */ + snprintf(p, sizeof(p), "%s/%s/index.theme", search_dir, dir); + if (ecore_file_exists(p)) + { + Efreet_Ini *ini; + char *d; + int skip = 0; + + ini = efreet_ini_new(p); + efreet_ini_section_set(ini, "Icon Theme"); + + //if (efreet_ini_boolean_get(ini, "Hidden")) skip = 1; + if (!efreet_ini_localestring_get(ini, "Name")) skip = 1; + efreet_ini_free(ini); + + if (!skip) + { + d = strdup(dir); + eina_hash_add(themes, dir, d); + } + } + free(dir); + } +} + +const char *system_icons[] = +{ + "address-book-new", + "application-exit", + "appointment-new", + "contact-new", + "dialog-apply", + "dialog-cancel", + "dialog-close", + "dialog-ok", + "document-new", + "document-open", + "document-open-recent", + "document-page-setup", + "document-print", + "document-print-preview", + "document-properties", + "document-revert", + "document-save", + "document-save-as", + "edit-copy", + "edit-cut", + "edit-delete", + "edit-find", + "edit-find-replace", + "edit-paste", + "edit-redo", + "edit-select-all", + "edit-undo", + "format-indent-less", + "format-indent-more", + "format-justify-center", + "format-justify-fill", + "format-justify-left", + "format-justify-right", + "format-text-direction-ltr", + "format-text-direction-rtl", + "format-text-bold", + "format-text-italic", + "format-text-underline", + "format-text-strikethrough", + "go-bottom", + "go-down", + "go-first", + "go-home", + "go-jump", + "go-last", + "go-next", + "go-previous", + "go-top", + "go-up", + "help-about", + "help-contents", + "help-faq", + "insert-image", + "insert-link", + "insert-object", + "insert-text", + "list-add", + "list-remove", + "mail-forward", + "mail-mark-important", + "mail-mark-junk", + "mail-mark-notjunk", + "mail-mark-read", + "mail-mark-unread", + "mail-message-new", + "mail-reply-all", + "mail-reply-sender", + "mail-send-receive", + "media-eject", + "media-playback-pause", + "media-playback-start", + "media-playback-stop", + "media-record", + "media-seek-backward", + "media-seek-forward", + "media-skip-backward", + "media-skip-forward", + "system-lock-screen", + "system-log-out", + "system-run", + "system-search", + "system-search", + "tools-check-spelling", + "view-fullscreen", + "view-refresh", + "view-sort-ascending", + "view-sort-descending", + "window-close", + "window-new", + "zoom-best-fit", + "zoom-in", + "zoom-original", + "zoom-out", + "process-working", + "accessories-calculator", + "accessories-character-map", + "accessories-dictionary", + "accessories-text-editor", + "help-browser", + "multimedia-volume-control", +#if 0 + "preferences-desktop-accessibility", + "preferences-desktop-font", + "preferences-desktop-keyboard", + "preferences-desktop-locale", + "preferences-desktop-multimedia", + "preferences-desktop-screensaver", + "preferences-desktop-theme", + "preferences-desktop-wallpaper", + "system-file-manager", + "system-software-update", + "utilities-terminal", + "applications-accessories", + "applications-development", + "applications-games", + "applications-graphics", + "applications-internet", + "applications-multimedia", + "applications-office", + "applications-other", + "applications-system", + "applications-utilities", + "preferences-desktop", + "preferences-desktop-accessibility", + "preferences-desktop-peripherals", + "preferences-desktop-personal", + "preferences-other", + "preferences-system", + "preferences-system-network", + "system-help", + "audio-card", + "audio-input-microphone", + "battery", + "camera-photo", + "camera-video", + "computer", + "drive-cdrom", + "drive-harddisk", + "drive-removable-media", + "input-gaming", + "input-keyboard", + "input-mouse", + "media-cdrom", + "media-floppy", + "multimedia-player", + "multimedia-player", + "network-wired", + "network-wireless", + "printer", + "emblem-default", + "emblem-documents", + "emblem-downloads", + "emblem-favorite", + "emblem-important", + "emblem-mail", + "emblem-photos", + "emblem-readonly", + "emblem-shared", + "emblem-symbolic-link", + "emblem-synchronized", + "emblem-system", + "emblem-unreadable", + "face-angel", + "face-crying", + "face-devil-grin", + "face-devil-sad", + "face-glasses", + "face-kiss", + "face-monkey", + "face-plain", + "face-sad", + "face-smile", + "face-smile-big", + "face-smirk", + "face-surprise", + "face-wink", + "application-x-executable", + "audio-x-generic", + "font-x-generic", + "image-x-generic", + "package-x-generic", + "text-html", + "text-x-generic", + "text-x-generic-template", + "text-x-script", + "video-x-generic", + "x-office-address-book", + "x-office-calendar", + "x-office-document", + "x-office-presentation", + "x-office-spreadsheet", + "folder", + "folder-remote", + "network-server", + "network-workgroup", + "start-here", + "user-desktop", + "user-home", + "user-trash", + "appointment-missed", + "appointment-soon", + "audio-volume-high", + "audio-volume-low", + "audio-volume-medium", + "audio-volume-muted", + "battery-caution", + "battery-low", + "dialog-error", + "dialog-information", + "dialog-password", + "dialog-question", + "dialog-warning", + "folder-drag-accept", + "folder-open", + "folder-visiting", + "image-loading", + "image-missing", + "mail-attachment", + "mail-unread", + "mail-read", + "mail-replied", + "mail-signed", + "mail-signed-verified", + "media-playlist-repeat", + "media-playlist-shuffle", + "network-error", + "network-idle", + "network-offline", + "network-receive", + "network-transmit", + "network-transmit-receive", + "printer-error", + "printer-printing", + "software-update-available", + "software-update-urgent", + "sync-error", + "sync-synchronizing", + "task-due", + "task-passed-due", + "user-away", + "user-idle", + "user-offline", + "user-online", + "user-trash-full", + "weather-clear", + "weather-clear-night", + "weather-few-clouds", + "weather-few-clouds-night", + "weather-fog", + "weather-overcast", + "weather-severe-alert", + "weather-showers", + "weather-showers-scattered", + "weather-snow", + "weather-storm", +#endif + NULL +}; + +int +ef_cb_efreet_icon_match(void) +{ + int i, ret = 1; + Eina_Hash *icon_hash; + Efreet_Icon_Theme *theme; + + theme = efreet_icon_theme_find(THEME); + icon_hash = eina_hash_string_superfast_new(free); + + ef_icons_find(theme, icon_hash); + + double start = ecore_time_get(); + for (i = 0; system_icons[i]; i++) + { + const char *path; + char *p, *s; + + path = efreet_icon_path_find(THEME, system_icons[i], SIZE); + + if (!path) + { +#if 1 + if (eina_hash_find(icon_hash, system_icons[i])) + { + printf("NOT FOUND %s\n", system_icons[i]); + ret = 0; + } +#endif + continue; + } + else if (!eina_hash_find(icon_hash, system_icons[i])) + { + printf("Found icon not in hash: %s\n", system_icons[i]); + } + + p = strdup(path); + s = strrchr(p, '.'); + if (s) *s = '\0'; + s = strrchr(p, '/'); + if (s) s++; + + if (s && strcmp(s, system_icons[i])) + { + printf("Name mismatch name (%s) vs ef (%s)\n", system_icons[i], s); + ret = 0; + } + free(p); + } + printf("Time: %f\n", (ecore_time_get() - start)); + eina_hash_free(icon_hash); + + start = ecore_time_get(); + for (i = 0; system_icons[i]; i++) + { + const char *path; + char *p, *s; + + path = efreet_icon_path_find(THEME, system_icons[i], SIZE); + + if (!path) continue; + p = strdup(path); + + s = strrchr(p, '.'); + if (s) *s = '\0'; + s = strrchr(p, '/'); + if (s) s++; + + if (s && strcmp(s, system_icons[i])) + { + printf("Name mismatch name (%s) vs ef (%s)\n", system_icons[i], s); + ret = 0; + } + free(p); + } + printf("Time: %f\n", (ecore_time_get() - start)); + + return ret; +} + +static void +ef_icons_find(Efreet_Icon_Theme *theme, Eina_Hash *icons) +{ + Eina_List *l, *ll; + char path[PATH_MAX]; + const char *theme_path; + + if (!theme || !icons) return; + + EINA_LIST_FOREACH(theme->paths, l, theme_path) + { + Efreet_Icon_Theme_Directory *dir; + + EINA_LIST_FOREACH(theme->directories, ll, dir) + { + snprintf(path, sizeof(path), "%s/%s/", theme_path, dir->name); + ef_read_dir(path, icons); + } + } + + if (theme->inherits) + { + Efreet_Icon_Theme *parent_theme; + char *parent; + + EINA_LIST_FOREACH(theme->inherits, l, parent) + { + parent_theme = efreet_icon_theme_find(parent); + if (parent_theme) + ef_icons_find(parent_theme, icons); + } + } + else if (strcmp(theme->name.internal, "hicolor")) + { + Efreet_Icon_Theme *parent_theme; + + parent_theme = efreet_icon_theme_find("hicolor"); + if (parent_theme) + ef_icons_find(parent_theme, icons); + } + + ef_read_dir("/usr/share/pixmaps", icons); +} + +static void +ef_read_dir(const char *dir, Eina_Hash *icons) +{ + Eina_List *files; + char *file; + + if (!dir || !icons) return; + + files = ecore_file_ls(dir); + if (!files) return; + + while ((file = eina_list_data_get(files))) + { + char *p; + + files = eina_list_remove_list(files, files); + p = strrchr(file, '.'); + if (!p) + { + FREE(file); + continue; + } + + if (!strcmp(p, ".png") || !strcmp(p, ".xpm")) + { + *p = '\0'; + + eina_hash_add(icons, file, strdup(file)); + } + + FREE(file); + } +} diff --git a/src/tests/ef_ini.c b/src/tests/ef_ini.c new file mode 100644 index 0000000..00d459e --- /dev/null +++ b/src/tests/ef_ini.c @@ -0,0 +1,174 @@ +#include "Efreet.h" +#include "config.h" +#include +#include +#include + +int +ef_cb_ini_parse(void) +{ + int ret = 1; + Efreet_Ini *ini; + + putenv("LC_ALL=en_US"); + + ini = efreet_ini_new(PKG_DATA_DIR"/test/test.ini"); + if (!ini) + { + printf("efreet_ini_parse() Failed to initialize Efreet_Ini\n"); + return 0; + } + + if (efreet_ini_section_set(ini, "contact")) + { + const char *val; + int ival; + unsigned int bval; + + val = efreet_ini_string_get(ini, "Name"); + if (!val || strcmp(val, "Foo Bar")) + { + printf("efreet_ini_string_get() Name parsed incorrectly\n"); + ret = 0; + } + + val = efreet_ini_localestring_get(ini, "Name"); + if (!val || strcmp(val, "English Foo Bar")) + { + printf("efreet_ini_localestring_get() Name parsed incorrectly\n"); + ret = 0; + } + + val = efreet_ini_string_get(ini, "Email"); + if (!val || strcmp(val, "foo@bar.com")) + { + printf("efreet_ini_string_get() Email parsed incorrectly\n"); + ret = 0; + } + + val = efreet_ini_localestring_get(ini, "Email"); + if (!val || strcmp(val, "foo@bar.com")) + { + printf("efreet_ini_localestring_get() Email parsed incorrectly\n"); + ret = 0; + } + + ival = efreet_ini_int_get(ini, "Age"); + if (ival != 30) + { + printf("efreet_ini_int_get() Age parsed incorrectly\n"); + ret = 0; + } + + bval = efreet_ini_boolean_get(ini, "TrueBoolean"); + if (!bval) + { + printf("efreet_ini_boolean_get() TrueBoolean parsed incorrectly\n"); + ret = 0; + } + + bval = efreet_ini_boolean_get(ini, "FalseBoolean"); + if (bval) + { + printf("efreet_ini_boolean_get() FalseBoolean parsed incorrectly\n"); + ret = 0; + } + + bval = efreet_ini_boolean_get(ini, "InvalidBoolean"); + if (bval) + { + printf("efreet_ini_boolean_get() InvalidBoolean parsed incorrectly\n"); + ret = 0; + } + + val = efreet_ini_string_get(ini, "Escaped"); + if (!val || strcmp(val, "line1\nline2\r\nline3\ttabbed \\ with a backslash and spaces")) + { + printf("efreet_ini_unescape() improperly unescaped value\n"); + ret = 0; + } + } + else + { + printf("efreet_ini_section_set() Failed to set 'contact' section\n"); + ret = 0; + } + + efreet_ini_free(ini); + + return ret; +} + +int +ef_cb_ini_long_line(void) +{ + Efreet_Ini *ini; + int ret = 1; + + struct + { + char *key; + int len; + } tests[] = { + {"key", 5099}, + {"key2", 5099}, + {NULL, 0} + }; + + ini = efreet_ini_new(PKG_DATA_DIR"/test/long.ini"); + if (!ini) + { + printf("Ini failed to parse.\n"); + ret = 0; + } + + if (ret) ret = efreet_ini_section_set(ini, "section"); + if (ret) + { + const char *val; + int i, len; + + for (i = 0; tests[i].key; i++) + { + val = efreet_ini_string_get(ini, tests[i].key); + if (val) + { + len = strlen(val); + if (len != tests[i].len) + { + printf("Invalid long line parsing. Value length: %d (expected %d)\n", len, tests[i].len); + ret = 0; + } + } + else + { + printf("Key missing: %s.", tests[i].key); + ret = 0; + } + } + } + else + { + printf("Section missing: 'section'."); + } + + if (ini) efreet_ini_free(ini); + return ret; +} + +int +ef_cb_ini_garbage(void) +{ + Efreet_Ini *ini; + int ret = 1; + + ini = efreet_ini_new(PKG_DATA_DIR"/test/test_garbage"); + if (!ini) + { + printf("Ini failed to parse.\n"); + return 0; + } + if (ini->data) ret = 0; + efreet_ini_free(ini); + return ret; +} diff --git a/src/tests/ef_locale.c b/src/tests/ef_locale.c new file mode 100644 index 0000000..cfc50e2 --- /dev/null +++ b/src/tests/ef_locale.c @@ -0,0 +1,85 @@ +#include "Efreet.h" +#include "efreet_private.h" +#include +#include +#include + +int +ef_cb_locale(void) +{ + int ret = 1, i; + struct + { + char *lc_message; + char *lang; + char *country; + char *modifier; + } langs[] = { + /* these are ordered such that when we move from LANG to LC_MESSAGES + * the LANG env will still be effect. Same with moving from + * LC_MESSAGES to LANG */ + {"LANG=", NULL, NULL, NULL}, + {"LANG=en", "en", NULL, NULL}, + {"LANG=en@Latn", "en", NULL, "Latn"}, + {"LANG=en_US", "en", "US", NULL}, + {"LANG=en_US@Latn", "en", "US", "Latn"}, + {"LANG=en_US.blah@Latn", "en", "US", "Latn"}, + {"LC_MESSAGES=", "en", "US", "Latn"}, /* This will fallback to LANG */ + {"LC_MESSAGES=fr", "fr", NULL, NULL}, + {"LC_MESSAGES=fr@Blah", "fr", NULL, "Blah"}, + {"LC_MESSAGES=fr_FR", "fr", "FR", NULL}, + {"LC_MESSAGES=fr_FR@Blah", "fr", "FR", "Blah"}, + {"LC_MESSAGES=fr_FR.Foo@Blah", "fr", "FR", "Blah"}, + {"LC_ALL=", "fr", "FR", "Blah"}, /* this will fallback to LC_MESSAGES */ + {"LC_ALL=sr", "sr", NULL, NULL}, + {"LC_ALL=sr@Ret", "sr", NULL, "Ret"}, + {"LC_ALL=sr_YU", "sr", "YU", NULL}, + {"LC_ALL=sr_YU@Ret", "sr", "YU", "Ret"}, + {"LC_ALL=sr_YU.ssh@Ret", "sr", "YU", "Ret"}, + {NULL, NULL, NULL, NULL} + }; + + /* reset everything to blank */ + putenv("LC_ALL="); + putenv("LC_MESSAGES="); + putenv("LANG="); + + for (i = 0; langs[i].lc_message; i++) + { + const char *tmp; + + putenv(langs[i].lc_message); + + tmp = efreet_lang_get(); + if ((langs[i].lang && (!tmp || strcmp(tmp, langs[i].lang))) + || (!langs[i].lang && tmp)) + { + printf("efreet_lang_get() is wrong (%s) with %s\n", + tmp, langs[i].lang); + ret = 0; + } + + tmp = efreet_lang_country_get(); + if ((langs[i].country && (!tmp || strcmp(tmp, langs[i].country))) + || (!langs[i].country && tmp)) + { + printf("efreet_lang_country_get() is wrong (%s) with %s\n", + tmp, langs[i].lang); + ret = 0; + } + + tmp = efreet_lang_modifier_get(); + if ((langs[i].modifier && (!tmp || strcmp(tmp, langs[i].modifier))) + || (!langs[i].modifier && tmp)) + { + printf("efreet_lang_modifier_get() is wrong with %s with %s\n", + tmp, langs[i].lang); + ret = 0; + } + + efreet_shutdown(); + efreet_init(); + } + + return ret; +} diff --git a/src/tests/ef_menu.c b/src/tests/ef_menu.c new file mode 100644 index 0000000..a7519b7 --- /dev/null +++ b/src/tests/ef_menu.c @@ -0,0 +1,150 @@ +#include "Efreet.h" +#include "config.h" +#include +#include + +#if 0 +static void +ef_menu_desktop_exec(Efreet_Menu *menu) +{ + Eina_List *l; + + if (menu->entries) + { + Efreet_Desktop *desktop; + + EINA_LIST_FOREACH(menu->entries, l, desktop) + efreet_desktop_exec(desktop, NULL); + } + if (menu->sub_menus) + { + Efreet_Menu *sub_menu; + + EINA_LIST_FOREACH(menu->sub_menus, l, sub_menu) + ef_menu_desktop_exec(sub_menu); + } +} +#endif + +int +ef_cb_menu_get(void) +{ + Efreet_Menu *menu; + + menu = efreet_menu_get(); +// menu = efreet_menu_parse(PKG_DATA_DIR"/test/test.menu"); + if (!menu) + { + printf("efreet_menu_get() returned NULL\n"); + return 0; + } + printf("\n"); + efreet_menu_dump(menu, ""); + efreet_menu_free(menu); + + return 1; +} + +int +ef_cb_menu_with_slashes(void) +{ + Efreet_Menu *menu; + + menu = efreet_menu_parse(PKG_DATA_DIR"/test/test_menu_slash_bad.menu"); + if (menu) + { + printf("efreet_menu_get() didn't return NULL\n"); + return 0; + } + + return 1; +} + +int +ef_cb_menu_save(void) +{ + Efreet_Menu *menu; + int ret; + +// menu = efreet_menu_get(); + menu = efreet_menu_parse(PKG_DATA_DIR"/test/test.menu"); + if (!menu) + { + printf("efreet_menu_get() returned NULL\n"); + return 0; + } + unlink("/tmp/test.menu"); + ret = efreet_menu_save(menu, "/tmp/test.menu"); + efreet_menu_free(menu); + return ret; +} + +int +ef_cb_menu_edit(void) +{ + Efreet_Menu *menu, *entry; + Efreet_Desktop *desktop; + +// menu = efreet_menu_get(); + menu = efreet_menu_parse(PKG_DATA_DIR"/test/test.menu"); + if (!menu) + { + printf("efreet_menu_get() returned NULL\n"); + return 0; + } +#if 0 + printf("\n"); + efreet_menu_dump(menu, ""); + printf("\n"); +#endif + + desktop = efreet_desktop_get(PKG_DATA_DIR"/test/test.desktop"); + if (!desktop) + { + efreet_menu_free(menu); + printf("No desktop found.\n"); + return 0; + } + + efreet_menu_desktop_insert(menu, desktop, 0); +#if 0 + printf("\n"); + efreet_menu_dump(menu, ""); + printf("\n"); +#endif + entry = eina_list_data_get(menu->entries); + if (desktop != entry->desktop) + { + efreet_menu_free(menu); + return 0; + } + + efreet_menu_desktop_insert(menu, desktop, 2); +#if 0 + printf("\n"); + efreet_menu_dump(menu, ""); + printf("\n"); +#endif + entry = eina_list_nth(menu->entries, 2); + if (desktop != entry->desktop) + { + efreet_menu_free(menu); + return 0; + } + + efreet_menu_desktop_insert(menu, desktop, -1); +#if 0 + printf("\n"); + efreet_menu_dump(menu, ""); + printf("\n"); +#endif + entry = eina_list_data_get(eina_list_last(menu->entries)); + if (desktop != entry->desktop) + { + efreet_menu_free(menu); + return 0; + } + + efreet_menu_free(menu); + return 1; +} diff --git a/src/tests/ef_mime.c b/src/tests/ef_mime.c new file mode 100644 index 0000000..2cb9e21 --- /dev/null +++ b/src/tests/ef_mime.c @@ -0,0 +1,57 @@ +#include "Efreet.h" +#include "Efreet_Mime.h" +#include "config.h" +#include +#include +#include +#include + +#define THEME "Tango" +#define SIZE 128 + +int +ef_mime_cb_get(void) +{ + const char *mime = NULL, *icon; + int misses = 0, i = 0; + struct + { + char *file; + char *mime; + } files[] = { + {PKG_DATA_DIR"/test/test_type.desktop", "application/x-desktop"}, + {PKG_DATA_DIR"/test/entry.png", "image/png"}, + {PKG_DATA_DIR"/test/entry", "image/png"}, + {PKG_DATA_DIR"/test/sub", "inode/directory"}, + {NULL, NULL} + }; + double start; + + if (!efreet_mime_init()) + { + printf("Could not init efreet\n"); + return 1; + } + + for (i = 0; files[i].file; ++i) + { + mime = efreet_mime_type_get(files[i].file); + if (!mime) + { + printf("Got %s as null instead of %s\n", files[i].file, files[i].mime); + misses ++; + } + else if (strcmp(mime, files[i].mime)) + { + printf("Got %s as %s instead of %s\n", files[i].file, mime, files[i].mime); + misses ++; + } + start = ecore_time_get(); + icon = efreet_mime_type_icon_get(files[i].mime, THEME, SIZE); + printf("mime icon: %s %s %f\n", files[i].mime, icon, ecore_time_get() - start); + } + + efreet_mime_shutdown(); + + return !misses; +} diff --git a/src/tests/ef_test.h b/src/tests/ef_test.h new file mode 100644 index 0000000..2e88c68 --- /dev/null +++ b/src/tests/ef_test.h @@ -0,0 +1,11 @@ +#ifndef EF_TEST +#define EF_TEST + +#include "config.h" + +#include + +#define IF_FREE(x) do { if (x) free(x); x = NULL; } while (0); +#define NEW(x, c) calloc(c, sizeof(x)) + +#endif diff --git a/src/tests/ef_utils.c b/src/tests/ef_utils.c new file mode 100644 index 0000000..0647ef0 --- /dev/null +++ b/src/tests/ef_utils.c @@ -0,0 +1,28 @@ +#include "Efreet.h" +#include + +int +ef_cb_utils(void) +{ + Efreet_Desktop *desktop; + const char *tmp2; + + printf("\n"); + + tmp2 = efreet_util_path_to_file_id("/usr/share/applications/this/tmp/test.desktop"); + if (tmp2) printf("%s\n", tmp2); + + desktop = efreet_util_desktop_file_id_find("kde-kresources.desktop"); + printf("kde-kresources.desktop: %p\n", desktop); + efreet_desktop_free(desktop); + + desktop = efreet_util_desktop_file_id_find("mplayer.desktop"); + printf("mplayer.desktop: %p\n", desktop); + efreet_desktop_free(desktop); + + desktop = efreet_util_desktop_file_id_find("nautilus-computer.desktop"); + printf("nautilus-computer.desktop: %p\n", desktop); + efreet_desktop_free(desktop); + + return 1; +} diff --git a/src/tests/efreet_icon_cache_dump.c b/src/tests/efreet_icon_cache_dump.c new file mode 100644 index 0000000..d1cb126 --- /dev/null +++ b/src/tests/efreet_icon_cache_dump.c @@ -0,0 +1,120 @@ +#ifdef HAVE_CONFIG_H +# include +#endif +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define EFREET_MODULE_LOG_DOM /* no logging in this file */ + +#include "Efreet.h" +#include "efreet_private.h" +#include "efreet_cache_private.h" + +int verbose = 0; + +static void +dump(Efreet_Icon_Theme *theme) +{ + Eet_File *ef; + unsigned int count = 0; + double start, avg; + char **keys; + int num, i; + + start = ecore_time_get(); + ef = eet_open(efreet_icon_cache_file(theme->name.internal), EET_FILE_MODE_READ); + printf("open: %s %f\n", theme->name.internal, ecore_time_get() - start); + + start = ecore_time_get(); + keys = eet_list(ef, "*", &num); + printf("list: %s %f\n", theme->name.internal, ecore_time_get() - start); + if (!keys) return; + + start = ecore_time_get(); + for (i = 0; i < num; i++) + { + Efreet_Cache_Icon *icon; + unsigned int j; + + icon = eet_data_read(ef, efreet_icon_edd(), keys[i]); + if (!icon) continue; + + for (j = 0; j < icon->icons_count; ++j) + count += icon->icons[j]->paths_count; + } + free(keys); + + start = ecore_time_get() - start; + avg = start / count; + printf("read: %s - %u paths (time: %f) (avg %f)\n", theme->name.internal, count, start, avg); + eet_close(ef); + eet_clearcache(); +} + +int +main(int argc, char **argv) +{ + Eet_File *theme_ef; + Eina_List *l = NULL; + Efreet_Icon_Theme *theme; + int i; + + efreet_cache_update = 0; + + if (!efreet_init()) return -1; + + theme_ef = eet_open(efreet_icon_theme_cache_file(), EET_FILE_MODE_READ); + if (!theme_ef) return -1; + + if (argc > 1) + { + for (i = 1; i < argc; i++) + { + theme = eet_data_read(theme_ef, efreet_icon_theme_edd(EINA_FALSE), argv[i]); + if (theme) l = eina_list_append(l, theme); + } + } + else + { + char **keys; + int num; + + keys = eet_list(theme_ef, "*", &num); + if (keys) + { + for (i = 0; i < num; i++) + { + theme = eet_data_read(theme_ef, efreet_icon_theme_edd(EINA_FALSE), keys[i]); + if (theme) l = eina_list_append(l, theme); + } + free(keys); + } + } + + EINA_LIST_FREE(l, theme) + { + void *data; + + dump(theme); + + /* free theme */ + eina_list_free(theme->paths); + eina_list_free(theme->inherits); + EINA_LIST_FREE(theme->directories, data) + free(data); + free(theme); + } + + efreet_shutdown(); + return 0; +} diff --git a/src/tests/efreet_spec_test.c b/src/tests/efreet_spec_test.c new file mode 100644 index 0000000..7c414b5 --- /dev/null +++ b/src/tests/efreet_spec_test.c @@ -0,0 +1,57 @@ +#include +#include +#include +#include "ef_test.h" + +static void dump(Efreet_Menu *menu, const char *path); + +int +main(int argc __UNUSED__, char **argv __UNUSED__) +{ + Efreet_Menu *menu; + + if (!efreet_init()) + { + fprintf(stderr, "Failed to init Efreet\n"); + return 1; + } + + menu = efreet_menu_get(); + if (!menu) + { + fprintf(stderr, "Failed to read menu\n"); + return 1; + } + + dump(menu, ""); + + efreet_menu_free(menu); + efreet_shutdown(); + return 0; +} + +static void +dump(Efreet_Menu *menu, const char *path) +{ + Efreet_Menu *entry; + Eina_List *l; + + if (!menu || !menu->entries) return; + + EINA_LIST_FOREACH(menu->entries, l, entry) + { + if (entry->type == EFREET_MENU_ENTRY_DESKTOP) + { + if (!path || !*path) path = "/"; + printf("%s\t%s\t%s\n", path, entry->id, + entry->desktop->orig_path); + } + else if (entry->type == EFREET_MENU_ENTRY_MENU) + { + char new_path[PATH_MAX]; + + snprintf(new_path, sizeof(new_path), "%s%s/", path, entry->name); + dump(entry, new_path); + } + } +} diff --git a/src/tests/efreet_suite.c b/src/tests/efreet_suite.c new file mode 100644 index 0000000..7424435 --- /dev/null +++ b/src/tests/efreet_suite.c @@ -0,0 +1,103 @@ +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include + +#include + +#include "efreet_suite.h" + +typedef struct _Efreet_Test_Case Efreet_Test_Case; + +struct _Efreet_Test_Case +{ + const char *test_case; + void (*build)(TCase *tc); +}; + +static const Efreet_Test_Case etc[] = { + { "Efreet", efreet_test_efreet }, + { "Efreet Cache", efreet_test_efreet_cache }, + { NULL, NULL } +}; + +static void +_list_tests(void) +{ + const Efreet_Test_Case *itr; + + itr = etc; + fputs("Available Test Cases:\n", stderr); + for (; itr->test_case; itr++) + fprintf(stderr, "\t%s\n", itr->test_case); +} + +static Eina_Bool +_use_test(int argc, const char **argv, const char *test_case) +{ + if (argc < 1) + return 1; + + for (; argc > 0; argc--, argv++) + if (strcmp(test_case, *argv) == 0) + return 1; + return 0; +} + +static Suite * +efreet_suite_build(int argc, const char **argv) +{ + TCase *tc; + Suite *s; + int i; + + s = suite_create("Efreet"); + + for (i = 0; etc[i].test_case; ++i) + { + if (!_use_test(argc, argv, etc[i].test_case)) continue; + tc = tcase_create(etc[i].test_case); + + etc[i].build(tc); + + suite_add_tcase(s, tc); + tcase_set_timeout(tc, 0); + } + + return s; +} + +int +main(int argc, char **argv) +{ + Suite *s; + SRunner *sr; + int i, failed_count; + + for (i = 1; i < argc; i++) + if ((strcmp(argv[i], "-h") == 0) || + (strcmp(argv[i], "--help") == 0)) + { + fprintf(stderr, "Usage:\n\t%s [test_case1 .. [test_caseN]]\n", + argv[0]); + _list_tests(); + return 0; + } + else if ((strcmp(argv[i], "-l") == 0) || + (strcmp(argv[i], "--list") == 0)) + { + _list_tests(); + return 0; + } + + s = efreet_suite_build(argc - 1, (const char **)argv + 1); + sr = srunner_create(s); + + srunner_run_all(sr, CK_NORMAL); + failed_count = srunner_ntests_failed(sr); + srunner_free(sr); + + return (failed_count == 0) ? 0 : 255; +} diff --git a/src/tests/efreet_suite.h b/src/tests/efreet_suite.h new file mode 100644 index 0000000..bfb47f9 --- /dev/null +++ b/src/tests/efreet_suite.h @@ -0,0 +1,10 @@ +#ifndef _EFREET_SUITE_H +#define _EFREET_SUITE_H + +#include + +void efreet_test_efreet(TCase *tc); +void efreet_test_efreet_cache(TCase *tc); + + +#endif /* _EFREET_SUITE_H */ diff --git a/src/tests/efreet_test_efreet.c b/src/tests/efreet_test_efreet.c new file mode 100644 index 0000000..d35ec9e --- /dev/null +++ b/src/tests/efreet_test_efreet.c @@ -0,0 +1,25 @@ +#ifdef HAVE_CONFIG_H +# include +#endif + +#include + +#include "efreet_suite.h" + + +START_TEST(efreet_test_efreet_init) +{ + int ret; + + ret = efreet_init(); + fail_if(ret != 1); + + ret = efreet_shutdown(); + fail_if(ret != 0); +} +END_TEST + +void efreet_test_efreet(TCase *tc) +{ + tcase_add_test(tc, efreet_test_efreet_init); +} diff --git a/src/tests/efreet_test_efreet_cache.c b/src/tests/efreet_test_efreet_cache.c new file mode 100644 index 0000000..2f8972d --- /dev/null +++ b/src/tests/efreet_test_efreet_cache.c @@ -0,0 +1,25 @@ +#ifdef HAVE_CONFIG_H +# include +#endif + +#include + +#include "efreet_suite.h" + + +START_TEST(efreet_test_efreet_cache_init) +{ + int ret; + + ret = efreet_init(); + fail_if(ret != 1); + + ret = efreet_shutdown(); + fail_if(ret != 0); +} +END_TEST + +void efreet_test_efreet_cache(TCase *tc) +{ + tcase_add_test(tc, efreet_test_efreet_cache_init); +} diff --git a/src/tests/main.c b/src/tests/main.c new file mode 100644 index 0000000..de6cf37 --- /dev/null +++ b/src/tests/main.c @@ -0,0 +1,188 @@ +#include "Efreet.h" +/* no logging */ +#define EFREET_MODULE_LOG_DOM +#include "efreet_private.h" +#include "Efreet_Mime.h" +#include "config.h" +#include +#include +#include +#include + +int ef_cb_efreet_data_home(void); +int ef_cb_efreet_config_home(void); +int ef_cb_efreet_cache_home(void); +int ef_cb_efreet_data_dirs(void); +int ef_cb_efreet_config_dirs(void); +int ef_cb_efreet_icon_theme(void); +int ef_cb_efreet_icon_theme_list(void); +int ef_cb_efreet_icon_match(void); +int ef_cb_ini_parse(void); +int ef_cb_ini_long_line(void); +int ef_cb_ini_garbage(void); +#if DEFAULT_VISIBILITY +int ef_cb_locale(void); +#endif +int ef_cb_desktop_parse(void); +int ef_cb_desktop_save(void); +int ef_cb_desktop_command_get(void); +int ef_cb_desktop_type_parse(void); +#if 0 +int ef_cb_desktop_file_id(void); +#endif +int ef_cb_menu_get(void); +int ef_cb_menu_with_slashes(void); +int ef_cb_menu_save(void); +#if 0 +int ef_cb_menu_edit(void); +#endif +int ef_cb_utils(void); +int ef_mime_cb_get(void); + +typedef struct Efreet_Test Efreet_Test; +struct Efreet_Test +{ + char *name; + int (*cb)(void); +}; + +static Efreet_Test tests[] = { + {"Data Home", ef_cb_efreet_data_home}, + {"Config Home", ef_cb_efreet_config_home}, + {"Cache Home", ef_cb_efreet_cache_home}, + {"Data Directories", ef_cb_efreet_data_dirs}, + {"Config Directories", ef_cb_efreet_config_dirs}, + {"Icon Theme Basic", ef_cb_efreet_icon_theme}, + {"Icon Theme List", ef_cb_efreet_icon_theme_list}, + {"Icon Matching", ef_cb_efreet_icon_match}, + {"INI Parsing", ef_cb_ini_parse}, + {"INI Long Line Parsing", ef_cb_ini_long_line}, + {"INI Garbage Parsing", ef_cb_ini_garbage}, +#if DEFAULT_VISIBILITY + {"Locale Parsing", ef_cb_locale}, +#endif + {"Desktop Parsing", ef_cb_desktop_parse}, + {"Desktop Type Parsing", ef_cb_desktop_type_parse}, + {"Desktop Save", ef_cb_desktop_save}, + {"Desktop Command", ef_cb_desktop_command_get}, +#if 0 + {"Desktop File ID", ef_cb_desktop_file_id}, +#endif + {"Menu Parsing", ef_cb_menu_get}, + {"Menu Incorrect Names", ef_cb_menu_with_slashes}, + {"Menu Save", ef_cb_menu_save}, +#if 0 + {"Menu Edit", ef_cb_menu_edit}, +#endif + {"Utils", ef_cb_utils}, + {"Mime", ef_mime_cb_get}, + {NULL, NULL} +}; + +extern char **environ; +static Eina_List *environment = NULL; + +void +environment_store(void) +{ + char *env; + char **e; +#ifdef HAVE_CLEARENV + EINA_LIST_FREE(environment, env) + free(env); + for (e = environ; *e; e++) + environment = eina_list_append(environment, strdup(*e)); +#endif +} + +void +environment_restore(void) +{ + Eina_List *l; + char *e; + if (!environment) return; +#ifdef HAVE_CLEARENV + clearenv(); + EINA_LIST_FOREACH(environment, l, e) + putenv(e); +#endif +} + +int +main(int argc, char ** argv) +{ + int i, passed = 0, num_tests = 0; + Eina_List *run = NULL; + double total; + char *env; + + eina_init(); + ecore_init(); + + total = ecore_time_get(); + if (argc > 1) + { + for (i = 1; i < argc; i++) + { + if ((!strcmp(argv[i], "-h")) || + (!strcmp(argv[i], "--help"))) + { + for (i = 0; tests[i].name; i++) + { + printf("%s\n", tests[i].name); + } + return 1; + } + run = eina_list_append(run, argv[i]); + } + } + + efreet_cache_update = 0; + environment_store(); + for (i = 0; tests[i].name; i++) + { + int ret; + double start; + + /* we've been given specific tests and it isn't in the list */ + if (run && !eina_list_search_unsorted(run, EINA_COMPARE_CB(strcasecmp), + tests[i].name)) + continue; + + if (!efreet_init()) + { + printf("Error initializing Efreet\n"); + continue; + } + + num_tests ++; + + printf("%s:\t\t", tests[i].name); + fflush(stdout); + start = ecore_time_get(); + ret = tests[i].cb(); + printf("%s in %.3f seconds\n", (ret ? "PASSED" : "FAILED"), + ecore_time_get() - start); + passed += ret; + + efreet_shutdown(); + environment_restore(); + } + + printf("\n-----------------\n"); +#ifdef HAVE_CLEARENV + clearenv(); + EINA_LIST_FREE(environment, env) + free(env); +#endif + printf("Passed %d of %d tests.\n", passed, num_tests); + + while (run) + run = eina_list_remove_list(run, run); + + printf("Total run: %.3f seconds\n", ecore_time_get() - total); + + ecore_shutdown(); + eina_shutdown(); + return 0; +} -- 2.7.4