/*
 * vim:ts=8:sw=3:sts=8:noexpandtab:cino=>5n-3f0^-2{2
 */
#include "e.h"

/* TODO List:
 * 
 * - If a .order file references a non-existing file, and the file
 *   is added in 'all', it doesn't show!
 * - track app execution state, visibility state etc. and call callbacks
 * - calls to execute an app or query its runing/starting state etc.
 * - clean up the add app functions. To much similar code.
 */

#define DEBUG 0
#define IDLE_ICONS 1

/* These two can be used to turn on or off the new and old border matching code.
 * There is a printf at the end of e_app_border_find that gives the times and results.
 * You can have both turned on at once for a better comparison.
 * Just never turn them both off.
 */
#define CLEVER_BORDERS 1
#define OLD_BORDERS 0
/* local subsystem functions */
typedef struct _E_App_Change_Info E_App_Change_Info;
typedef struct _E_App_Callback    E_App_Callback;

struct _E_App_Change_Info
{
   E_App        *app;
   E_App_Change  change;
};

struct _E_App_Callback
{
   void (*func) (void *data, E_App *a, E_App_Change ch);
   void  *data;
   unsigned char delete_me : 1;
};

struct _E_App_Hash_Idler
{
   Ecore_Idler *idler;
   /* We keep a seperate list so that nothing external can change it between 
    * idler calls.  Means we might miss some during the idler run, but since 
    * this is just an optimization pre load thing, then that's no problem.
    */
   Evas_List   *apps;
   double       begin, pass;
};

static int       _e_apps_hash_idler_cb     (void *data);
static void      _e_app_free               (E_App *a);
static E_App     *_e_app_subapp_file_find  (E_App *a, const char *file);
static void      _e_app_change             (E_App *a, E_App_Change ch);
static int       _e_apps_cb_exit           (void *data, int type, void *event);
static void      _e_app_cb_monitor         (void *data, Ecore_File_Monitor *em, Ecore_File_Event event, const char *path);
static void      _e_app_subdir_rescan      (E_App *app);
static int       _e_app_is_eapp            (const char *path);
static int       _e_app_copy               (E_App *dst, E_App *src);
static E_App_Icon_Type _e_app_icon_type    (E_App *a);
static void      _e_app_fields_save_others(E_App *a);
static void      _e_app_save_order         (E_App *app);
static int       _e_app_cb_event_border_add(void *data, int type, void *event);
static int       _e_app_cb_expire_timer    (void *data);
#if DEBUG
static void      _e_app_print              (const char *path, Ecore_File_Event event);
#endif
static void      _e_app_check_order        (const char *file);
static int       _e_app_order_contains     (E_App *a, const char *file);
static void      _e_app_resolve_file_name  (char *buf, size_t size, const char *path, const char *file);

/* local subsystem globals */
static int          _e_apps_callbacks_walking = 0;
static int          _e_apps_callbacks_delete_me = 0;
static Evas_List   *_e_apps_change_callbacks = NULL;
static Ecore_Event_Handler *_e_apps_exit_handler = NULL;
static Ecore_Event_Handler *_e_apps_border_add_handler = NULL;
static E_App       *_e_apps_all = NULL;
static const char  *_e_apps_path_all = NULL;
static const char  *_e_apps_path_trash = NULL;
static Evas_List   *_e_apps_start_pending = NULL;
static Evas_Hash   *_e_apps_every_app = NULL;
static struct _E_App_Hash_Idler _e_apps_hash_idler;

#if CLEVER_BORDERS
struct _E_App_Glob_List_Entry
{
   const char *key;
   const char *path;
};

struct _E_App_Glob_List_Winner
{
   const char *path;
   int         ok;
};

static Evas_Hash *_e_apps_border_ng_win_class = NULL, 
                 *_e_apps_border_ng_win_title = NULL, 
		 *_e_apps_border_ng_win_name = NULL, 
		 *_e_apps_border_ng_win_role = NULL, 
		 *_e_apps_border_ng_exe = NULL;
static Evas_List *_e_apps_border_g_win_class = NULL, 
                 *_e_apps_border_g_win_title = NULL, 
		 *_e_apps_border_g_win_name = NULL, 
		 *_e_apps_border_g_win_role = NULL;
static double border_setup_time = 0.0;
static int    border_setup_count = 0, glob_count = 0;
#endif
static double border_time = 0.0, border_clever_time = 0.0;
static int    border_count = 0;

/* externally accessible functions */
EAPI int
e_app_init(void)
{
   Ecore_List  *_e_apps_all_filenames = NULL;
   const char *home;
   char buf[PATH_MAX];
   double begin;

   home = e_user_homedir_get();
   snprintf(buf, sizeof(buf), "%s/.e/e/applications/trash", home);
   _e_apps_path_trash = evas_stringshare_add(buf);
   snprintf(buf, sizeof(buf), "%s/.e/e/applications/all", home);
   _e_apps_path_all = evas_stringshare_add(buf);
   _e_apps_exit_handler = ecore_event_handler_add(ECORE_EXE_EVENT_DEL, _e_apps_cb_exit, NULL);
   _e_apps_border_add_handler = ecore_event_handler_add(E_EVENT_BORDER_ADD, _e_app_cb_event_border_add, NULL);
#if CLEVER_BORDERS
#endif
   /* Prefill with empty E_Apps from the all directory. */
   ecore_desktop_instrumentation_reset();
   begin = ecore_time_get();
   _e_apps_all = e_app_new(_e_apps_path_all, 0);
   _e_apps_all_filenames = ecore_file_ls(_e_apps_path_all);
   if (_e_apps_all_filenames)
     {
        const char *file;

        while ((file = ecore_list_next(_e_apps_all_filenames)))
	  {
             if (_e_app_is_eapp(file))
	       {
	          E_App *app;

                  snprintf(buf, sizeof(buf), "%s/%s", _e_apps_path_all, file);
                  app = e_app_empty_new(buf);
	          if ((app) && (app->path))
	            {
	               _e_apps_every_app = evas_hash_direct_add(_e_apps_every_app, app->path, app);
                       app->parent = _e_apps_all;
                       _e_apps_all->subapps = evas_list_prepend(_e_apps_all->subapps, app);
		       _e_apps_hash_idler.apps = evas_list_append(_e_apps_hash_idler.apps, app);
                       e_object_ref(E_OBJECT(app));
	            }
	       }
	  }
        ecore_list_destroy(_e_apps_all_filenames);
     }
#if DEBUG
   printf("INITIAL APP SCAN %3.3f\n", ecore_time_get() - begin);
#endif
   ecore_desktop_instrumentation_print();
   ecore_desktop_instrumentation_reset();
   _e_apps_hash_idler.begin = ecore_time_get();
   _e_apps_hash_idler.idler = ecore_idler_add(_e_apps_hash_idler_cb, &_e_apps_hash_idler);
   return 1;
}

static int
_e_apps_hash_idler_cb(void *data)
{
   struct _E_App_Hash_Idler *idler;

   idler = data;
   idler->pass = ecore_time_get();
   while (idler->apps != NULL)
     {
        E_App *a;
        double t;

        a = idler->apps->data;
        E_OBJECT_CHECK(a);
        E_OBJECT_TYPE_CHECK(a, E_APP_TYPE);
        if ((!a->idle_fill) && (!a->filled))
          {
             struct stat         st;

             a->idle_fill = 1;
	     if (stat(a->path, &st) >= 0)
                a->mtime = st.st_mtime;
             e_app_fields_fill(a, a->path);
          }
#if IDLE_ICONS
        if ((!a->idle_icon) && (a->icon_type == E_APP_ICON_UNKNOWN))
          {
             a->idle_icon = 1;
             _e_app_icon_type(a);
          }
#endif
        /* FIXME: This time should be since the beginnig of all idler processing, 
         * and the time limit should be tied to the frame rate. 
         */
        idler->apps = evas_list_remove_list(idler->apps, idler->apps);
        t = ecore_time_get() - idler->pass;
        if (t > 0.02)   break;
     }
   if (idler->apps == NULL)
     {
#if DEBUG
        printf("\nIDLE APP FILLING SCAN %3.3f\n", ecore_time_get() - idler->begin);
#if CLEVER_BORDERS
        printf("APP BORDER SETUP %3.6f (average %2.6f) %d globs\n", border_setup_time, border_setup_time / border_setup_count, glob_count);
#endif
#endif
	idler->idler = NULL;
        ecore_desktop_instrumentation_print();
        return 0;
     }
   return 1;
}


#if CLEVER_BORDERS
static Evas_Bool
_e_apps_border_hash_cb_free(Evas_Hash *hash, const char *key, void *data, void *fdata)
{
   Evas_List *list;

   list = data;
   evas_list_free(list);
   return 0;
}
#endif

EAPI int
e_app_shutdown(void)
{
   Evas_List *list;

   if (_e_apps_hash_idler.idler)
     {
        ecore_idler_del(_e_apps_hash_idler.idler);
	_e_apps_hash_idler.idler = NULL;
     }
   _e_apps_start_pending = evas_list_free(_e_apps_start_pending);
   if (_e_apps_all)
     {
        list = _e_apps_all->subapps;
	e_object_unref(E_OBJECT(_e_apps_all));
	_e_apps_all = NULL;
     }
   if (_e_apps_exit_handler)
     {
	ecore_event_handler_del(_e_apps_exit_handler);
	_e_apps_exit_handler = NULL;
     }
   if (_e_apps_border_add_handler)
     {
	ecore_event_handler_del(_e_apps_border_add_handler);
	_e_apps_border_add_handler = NULL;
     }
   evas_stringshare_del(_e_apps_path_trash);
   evas_stringshare_del(_e_apps_path_all);
     {
	Evas_List *l;

	for (l = list; l; l = l->next)
	  {
	     E_App *a;
	     a = l->data;
	     printf("BUG: References %d %s\n", E_OBJECT(a)->references, a->path);
	  }
     }
   evas_hash_free(_e_apps_every_app);
   _e_apps_every_app = NULL;
#if CLEVER_BORDERS
   for (list = _e_apps_border_g_win_name; list; list = list->next)
     {
	struct _E_App_Glob_List_Entry *entry;

	entry = list->data;
	if (entry)   free(entry);
	list->data = NULL;
     }
   for (list = _e_apps_border_g_win_class; list; list = list->next)
     {
	struct _E_App_Glob_List_Entry *entry;

	entry = list->data;
	if (entry)   free(entry);
	list->data = NULL;
     }
   for (list = _e_apps_border_g_win_title; list; list = list->next)
     {
	struct _E_App_Glob_List_Entry *entry;

	entry = list->data;
	if (entry)   free(entry);
	list->data = NULL;
     }
   for (list = _e_apps_border_g_win_role; list; list = list->next)
     {
	struct _E_App_Glob_List_Entry *entry;

	entry = list->data;
	if (entry)   free(entry);
	list->data = NULL;
     }

   evas_hash_foreach(_e_apps_border_ng_win_name, _e_apps_border_hash_cb_free, NULL);
   evas_hash_foreach(_e_apps_border_ng_win_class, _e_apps_border_hash_cb_free, NULL);
   evas_hash_foreach(_e_apps_border_ng_win_title, _e_apps_border_hash_cb_free, NULL);
   evas_hash_foreach(_e_apps_border_ng_win_role, _e_apps_border_hash_cb_free, NULL);

   evas_hash_free(_e_apps_border_ng_win_name);    _e_apps_border_ng_win_name = NULL;
   evas_hash_free(_e_apps_border_ng_win_class);   _e_apps_border_ng_win_class = NULL;
   evas_hash_free(_e_apps_border_ng_win_title);   _e_apps_border_ng_win_title = NULL;
   evas_hash_free(_e_apps_border_ng_win_role);    _e_apps_border_ng_win_role = NULL;
   evas_hash_free(_e_apps_border_ng_exe);         _e_apps_border_ng_exe = NULL;

   evas_list_free(_e_apps_border_g_win_name);     _e_apps_border_g_win_name = NULL;
   evas_list_free(_e_apps_border_g_win_class);    _e_apps_border_g_win_class = NULL;
   evas_list_free(_e_apps_border_g_win_title);    _e_apps_border_g_win_title = NULL;
   evas_list_free(_e_apps_border_g_win_role);     _e_apps_border_g_win_role = NULL;
#endif
   return 1;
}


EAPI void
e_app_unmonitor_all(void)
{
   Evas_List *l;
   
   for (l = _e_apps_all->subapps; l; l = l->next)
     {
	E_App *a;

	a = l->data;
	if (a->monitor)
	  {
	     ecore_file_monitor_del(a->monitor);
	     a->monitor = NULL;
	  }
     }
}

/* FIXME: Not actualy used anywhere, should we nuke it or should we use it everywhere that an E_App is allocated? */
EAPI E_App *
e_app_raw_new(void)
{
   E_App *a;
   
   a = E_OBJECT_ALLOC(E_App, E_APP_TYPE, _e_app_free);
   return a;
}

EAPI E_App *
e_app_new(const char *path, int scan_subdirs)
{
   E_App *a;
   E_App *a2;
   struct stat         st;
   int                 stated = 0;
   int                 new_app = 0;
   int                 virtual_app = 0;
   int                 in_all = 0;
   char buf[PATH_MAX];

   if (!path)   return NULL;

   a = e_app_path_find(path);
   /* Is it a virtual path from inside a .order file? */
   if ((!a) && (!ecore_file_exists(path)))
     {
        _e_app_resolve_file_name(buf, sizeof(buf), _e_apps_path_all, ecore_file_get_file(path));
        if (ecore_file_exists(buf))
          {
             path = buf;
             a = e_app_path_find(path);
             in_all = 1;
             virtual_app = 1;
          }
     }

   /* Check if the cache is still valid. */
   if (a)
     {
	if (stat(a->path, &st) >= 0)
	  {
	     stated = 1;
	     if (st.st_mtime > a->mtime)
	       {
	          e_app_fields_empty(a);
#if DEBUG
		  printf("M");
#endif
	       }
#if DEBUG
	     else
	        printf("H");
#endif
	  }
	e_object_ref(E_OBJECT(a));
     }
   
   if ((!a) && (ecore_file_exists(path)))
     {
        /* Create it. */
        a = e_app_empty_new(path);
	new_app = 1;
#if DEBUG
        printf("+");
#endif
     }

   if ((a) && (a->path))
     {
	if ((!in_all) && (strncmp(a->path, _e_apps_path_all, strlen(_e_apps_path_all)) == 0))
	  in_all = 1;
	if (ecore_file_is_dir(a->path))
	  {
	     if ((!a->idle_fill) && (!a->filled))
	       {
		  snprintf(buf, sizeof(buf), "%s/.directory", path);
		  if (ecore_file_exists(buf))
		    e_app_fields_fill(a, buf);
		  /* Not all .directory files are created equal, some unknown file manager can create bogus ones. */
		  if (!a->filled)
		    {
		       a->name = evas_stringshare_add(ecore_file_get_file(a->path));
		       a->filled = 1;
		       a->idle_fill = 1;
		    }
	       }
	     if (!a->filled) goto error;
	     if (scan_subdirs)
	       {
		  if (stated)
		    _e_app_subdir_rescan(a);
		  else
		    e_app_subdir_scan(a, scan_subdirs);
	       }
	     
	     /* Don't monitor the all directory, all changes to that must go through e_app. */
	     if ((!stated) && (strcmp(_e_apps_path_all, a->path) != 0))
	       a->monitor = ecore_file_monitor_add(a->path, _e_app_cb_monitor, a);
	  }
	else if (_e_app_is_eapp(a->path))
	  {
	     if ((!a->idle_fill) && (!a->filled))
	       e_app_fields_fill(a, a->path);
	     
	     /* no exe field.. not valid. drop it */
	     //		  if (!_e_app_exe_valid_get(a->exe))
	     //		     goto error;
	  }
	else
	  goto error;
	
        if (virtual_app)
	  {
	     a2 = E_OBJECT_ALLOC(E_App, E_APP_TYPE, _e_app_free);
	     if (a2)
	       {
		  if (_e_app_copy(a2, a))
		    a->references = evas_list_append(a->references, a2);
		  else
		    {
		       e_object_del(E_OBJECT(a2));
		       goto error;
		    }
	       }
	     else
	       goto error;
	  }
	/* Timestamp the cache, and no need to stat the file twice if the cache was stale. */
	if ((stated) || (stat(a->path, &st) >= 0))
	  {
	     a->mtime = st.st_mtime;
	     stated = 1;
	  }
	
	if (new_app)
	  {
	     _e_apps_every_app = evas_hash_direct_add(_e_apps_every_app, a->path, a);
             /* Link it in to all if needed. */
             if ((_e_apps_all) && (a != _e_apps_all) && (in_all))
               {
                  a->parent = _e_apps_all;
                  e_object_ref(E_OBJECT(a));
               }
	  }
	else if ((_e_apps_all) && (_e_apps_all->subapps) && (in_all))
	  _e_apps_all->subapps = evas_list_remove(_e_apps_all->subapps, a);
        if ((_e_apps_all) && (a != _e_apps_all) && (in_all))
	  _e_apps_all->subapps = evas_list_prepend(_e_apps_all->subapps, a);

        if (virtual_app)
	  a = a2;
	
     }
   else
     goto error;
   
   return a;
   
   error:
   if (a)
     {
	if (a->monitor) ecore_file_monitor_del(a->monitor);
	if (a->path) evas_stringshare_del(a->path);
	/* Just going that little extra paranoid, coz rephorm got a strange seggie. */
	a->monitor = NULL;
	a->path = NULL;
	e_app_fields_empty(a);
	free(a);
     }
   return NULL;
}

EAPI E_App *
e_app_empty_new(const char *path)
{
   E_App *a;
   
   a = E_OBJECT_ALLOC(E_App, E_APP_TYPE, _e_app_free);
   if (!a) return NULL;
   /* record the path, or make one up */
   if (path) a->path = evas_stringshare_add(path);   
   else if (_e_apps_path_all)
     {
	char buf[PATH_MAX];
	
	snprintf(buf, sizeof(buf), "%s/_new_app_%1.1f.desktop", _e_apps_path_all, ecore_time_get());
	a->path = evas_stringshare_add(buf);
     }
   a->icon_type = E_APP_ICON_UNKNOWN;
#if DEBUG
   printf("NEW APP %p %s\n", a, a->path);
#endif
   return a;
}

EAPI void
e_app_image_size_set(E_App *a, int w, int h)
{
   a->width = w;
   a->height = h;
}

EAPI int
e_app_is_parent(E_App *parent, E_App *app)
{
   E_App *current;

   current = app->parent;

   while (current)
     {
	if (current == parent)
	  return 1;
	current = current->parent;
     }

   return 0;
}

EAPI int
e_app_equals(E_App *app1, E_App *app2)
{
   if ((!app1) || (!app2)) return 0;
   return ((app1 == app2) || (app1->orig == app2) ||
	   (app1 == app2->orig) || (app1->orig == app2->orig));
}

EAPI void
e_app_subdir_scan(E_App *a, int scan_subdirs)
{
   Ecore_List *files;
   char *s;
   char buf[PATH_MAX];
   
   E_OBJECT_CHECK(a);
   E_OBJECT_TYPE_CHECK(a, E_APP_TYPE);
   /* FIXME: This is probably the wrong test. */
   if (a->exe) return;
   if (a->scanned)
     {
	Evas_List *l;
	
	if (!scan_subdirs) return;
	for (l = a->subapps; l; l = l->next)
	  e_app_subdir_scan(l->data, scan_subdirs);
	return;
     }
   a->scanned = 1;
   files = e_app_dir_file_list_get(a);
   if (files)
     {
	while ((s = ecore_list_next(files)))
	  {
	     E_App *a2;

	     a2 = NULL;

             _e_app_resolve_file_name(buf, sizeof(buf), a->path, s);
	     a2 = e_app_new(buf, scan_subdirs);
	     if (a2)
	       {
	          a2->parent = a;
		  a->subapps = evas_list_append(a->subapps, a2);
                  e_object_ref(E_OBJECT(a2));
	       }
	  }
	ecore_list_destroy(files);
     }
}

EAPI int
e_app_exec(E_App *a, int launch_id)
{
   Ecore_Exe *exe;
   E_App *original;
   E_App_Instance *inst;
   Evas_List *l;
   char *command;
   
   E_OBJECT_CHECK_RETURN(a, 0);
   E_OBJECT_TYPE_CHECK_RETURN(a, E_APP_TYPE, 0);
   if (!a->exe) return 0;

  /* no exe field, don't exe it. */
//   if (!_e_app_exe_valid_get(a->exe))
//     return 0;
   
   /* FIXME: set up locale, encoding and input method env vars if they are in
    * the eapp file */
   inst = E_NEW(E_App_Instance, 1);
   if (!inst) return 0;
   
   if (a->orig)
     original = a->orig;
   else
     original = a;
   
   if (a->desktop)
     {
        Ecore_List *commands;

        /* We are not passing a list of files, so we only expect one command. */
        commands = ecore_desktop_get_command(a->desktop, NULL, 1);
	if (commands)
	  {
	     char *temp;

	     temp = ecore_list_first(commands);
	     if (temp)
	        command = strdup(temp);
	     ecore_list_destroy(commands);
	  }
     }
   else
     command = strdup(a->exe);
   if (!command)
     {
	free(inst);
	e_util_dialog_show(_("Run Error"),
			   _("Enlightenment was unable to process a command line:<br>"
			     "<br>"
			     "%s %s<br>"),
			   a->exe, (a->exe_params != NULL) ? a->exe_params : "" );
	return 0;
     }
   /* We want the stdout and stderr as lines for the error dialog if it exits abnormally. */
   e_util_library_path_strip();
   exe = ecore_exe_pipe_run(command, ECORE_EXE_PIPE_AUTO | ECORE_EXE_PIPE_READ | ECORE_EXE_PIPE_ERROR | ECORE_EXE_PIPE_READ_LINE_BUFFERED | ECORE_EXE_PIPE_ERROR_LINE_BUFFERED, inst);
   e_util_library_path_restore();
   if (!exe)
     {
	free(command);
	free(inst);
	e_util_dialog_show(_("Run Error"),
			   _("Enlightenment was unable to fork a child process:<br>"
			     "<br>"
			     "%s %s<br>"),
			   a->exe, (a->exe_params != NULL) ? a->exe_params : "" );
	return 0;
     }
   /* 20 lines at start and end, 20x100 limit on bytes at each end. */
   ecore_exe_auto_limits_set(exe, 2000, 2000, 20, 20);
   ecore_exe_tag_set(exe, "E/app");
   inst->app = original;
   inst->exe = exe;
   inst->launch_id = launch_id;
   inst->launch_time = ecore_time_get();
   inst->expire_timer = ecore_timer_add(10.0, _e_app_cb_expire_timer, inst);

   if (original->parent == _e_apps_all)
     {
	_e_apps_all->subapps = evas_list_remove(_e_apps_all->subapps, original);
	_e_apps_all->subapps = evas_list_prepend(_e_apps_all->subapps, original);
     }

   original->instances = evas_list_append(original->instances, inst);
   _e_apps_start_pending = evas_list_append(_e_apps_start_pending, original);
   if (original->startup_notify) original->starting = 1;
   for (l = original->references; l; l = l->next)
     {
	E_App *a2;
	
	a2 = l->data;
	_e_app_change(a2, E_APP_EXEC);
     }
   _e_app_change(original, E_APP_EXEC);
   free(command);
   return 1;
}

EAPI int
e_app_starting_get(E_App *a)
{
   E_OBJECT_CHECK_RETURN(a, 0);
   E_OBJECT_TYPE_CHECK_RETURN(a, E_APP_TYPE, 0);
   return a->starting;
}

EAPI int
e_app_running_get(E_App *a)
{
   E_OBJECT_CHECK_RETURN(a, 0);
   E_OBJECT_TYPE_CHECK_RETURN(a, E_APP_TYPE, 0);
   if (a->orig)
     a = a->orig;
   if (a->instances) return 1;
   return 0;
}

static void
_e_app_list_prepend_relative(E_App *add, E_App *before, E_App *parent)
{
   FILE *f;
   char buf[PATH_MAX];
   Evas_List *l;
   
   if ((!add) || (!parent)) return;
   snprintf(buf, sizeof(buf), "%s/.order", parent->path);
   f = fopen(buf, "wb");
   if (!f) return;

   for (l = parent->subapps; l; l = l->next)
     {
	E_App *a;

	a = l->data;
	if (a == before) fprintf(f, "%s\n", ecore_file_get_file(add->path));
	fprintf(f, "%s\n", ecore_file_get_file(a->path));
     }
   if (before == NULL) fprintf(f, "%s\n", ecore_file_get_file(add->path));
   fclose(f);
}

static void
_e_app_files_list_prepend_relative(Evas_List *files, E_App *before, E_App *parent)
{
   FILE *f;
   char buf[PATH_MAX];
   Evas_List *l, *l2;
   
   if ((!files) || (!parent)) return;
   snprintf(buf, sizeof(buf), "%s/.order", parent->path);
   f = fopen(buf, "wb");
   if (!f) return;

   for (l = parent->subapps; l; l = l->next)
     {
	E_App *a;
	
	a = l->data;
	if (a == before)
	  {
	     /* Add the new files */
	     for (l2 = files; l2; l2 = l2->next)
	       {
		  char *file;

		  file = l2->data;
		  fprintf(f, "%s\n", ecore_file_get_file(file));
	       }
	  }
	fprintf(f, "%s\n", ecore_file_get_file(a->path));
     }
   if (before == NULL)
     {
	/* Add the new files */
	for (l2 = files; l2; l2 = l2->next)
	  {
	     char *file;
	     
	     file = l2->data;
	     fprintf(f, "%s\n", ecore_file_get_file(file));
	  }
     }
   fclose(f);
}

static void
_e_app_files_download(Evas_List *files)
{
   Evas_List *l;
   
   for (l = files; l; l = l->next)
     {
	char *file;
	char buf[PATH_MAX];
	
	file = l->data;
	if (!_e_app_is_eapp(file)) continue;
        snprintf(buf, sizeof(buf), "%s/%s", _e_apps_path_all,
		 ecore_file_get_file(file));
	if (!ecore_file_download(file, buf, NULL, NULL, NULL)) continue;
     }
}

EAPI void
e_app_list_prepend_relative(E_App *add, E_App *before)
{
   if ((!add) || (!before)) return;
   if (!before->parent) return;
   _e_app_list_prepend_relative(add, before, before->parent);
}

EAPI void
e_app_list_append(E_App *add, E_App *parent)
{
   if ((!add) || (!parent)) return;
   _e_app_list_prepend_relative(add, NULL, parent);
}

EAPI void
e_app_files_list_prepend_relative(Evas_List *files, E_App *before)
{
   _e_app_files_download(files);
   /* Force rescan of all subdir */
   _e_app_subdir_rescan(_e_apps_all);
   /* Change .order file */
   _e_app_files_list_prepend_relative(files, before, before->parent);
}

EAPI void
e_app_files_list_append(Evas_List *files, E_App *parent)
{
   _e_app_files_download(files);
   /* Force rescan of all subdir */
   _e_app_subdir_rescan(_e_apps_all);
   /* Change .order file */
   _e_app_files_list_prepend_relative(files, NULL, parent);
}

EAPI void
e_app_prepend_relative(E_App *add, E_App *before)
{
   char buf[PATH_MAX];

   if ((!add) || (!before)) return;
   if (!before->parent) return;

   before->parent->subapps = evas_list_prepend_relative(before->parent->subapps,
							add, before);
   e_object_ref(E_OBJECT(add));
   add->parent = before->parent;

   /* Check if this app is in the trash */
   if (!strncmp(add->path, _e_apps_path_trash, strlen(_e_apps_path_trash)))
     {
	/* Move to all */
	snprintf(buf, sizeof(buf), "%s/%s", _e_apps_path_all, ecore_file_get_file(add->path));
	if (ecore_file_exists(buf))
	  snprintf(buf, sizeof(buf), "%s/%s", before->parent->path, ecore_file_get_file(add->path));
	ecore_file_mv(add->path, buf);
	evas_stringshare_del(add->path);
	add->path = evas_stringshare_add(buf);
     }

   _e_app_save_order(before->parent);
   _e_app_change(add, E_APP_ADD);
   _e_app_change(before->parent, E_APP_ORDER);
}

EAPI void
e_app_append(E_App *add, E_App *parent)
{
   char buf[PATH_MAX];

   if ((!add) || (!parent)) return;
   parent->subapps = evas_list_append(parent->subapps, add);
   e_object_ref(E_OBJECT(add));
   add->parent = parent;

   /* Check if this app is in the trash */
   if (!strncmp(add->path, _e_apps_path_trash, strlen(_e_apps_path_trash)))
     {
	/* Move to all */
	snprintf(buf, sizeof(buf), "%s/%s", _e_apps_path_all, ecore_file_get_file(add->path));
	if (ecore_file_exists(buf))
	  snprintf(buf, sizeof(buf), "%s/%s", parent->path, ecore_file_get_file(add->path));
	ecore_file_mv(add->path, buf);
        _e_apps_every_app = evas_hash_del(_e_apps_every_app, add->path, add);
	evas_stringshare_del(add->path);
	add->path = evas_stringshare_add(buf);
        _e_apps_every_app = evas_hash_direct_add(_e_apps_every_app, add->path, add);
     }

   _e_app_save_order(parent);
   _e_app_change(add, E_APP_ADD);
}

EAPI void
e_app_files_prepend_relative(Evas_List *files, E_App *before)
{
   Evas_List *l;

   if (!before) return;
   if (!before->parent) return;

   _e_app_files_download(files);
   /* Force rescan of all subdir */
   _e_app_subdir_rescan(_e_apps_all);
   /* Change .order file */
   if (before->parent != _e_apps_all)
     {
	FILE *f;
	char buf[PATH_MAX];

	snprintf(buf, sizeof(buf), "%s/.order", before->parent->path);
	f = fopen(buf, "wb");
	if (!f) return;

	for (l = before->parent->subapps; l; l = l->next)
	  {
	     E_App *a;
	     Evas_List *l2;

	     a = l->data;
	     if (a == before)
	       {
		  /* Add the new files */
		  for (l2 = files; l2; l2 = l2->next)
		    {
		       char *file;

		       file = l2->data;
		       fprintf(f, "%s\n", ecore_file_get_file(file));
		    }
	       }
	     fprintf(f, "%s\n", ecore_file_get_file(a->path));
	  }
	fclose(f);
     }
}

EAPI void
e_app_files_append(Evas_List *files, E_App *parent)
{
   Evas_List *l, *subapps;

   if (!parent) return;
   subapps = parent->subapps;

   _e_app_files_download(files);
   /* Force rescan of all subdir */
   _e_app_subdir_rescan(_e_apps_all);
   /* Change .order file */
   if (parent != _e_apps_all)
     {
	FILE *f;
	char buf[PATH_MAX];

	snprintf(buf, sizeof(buf), "%s/.order", parent->path);
	f = fopen(buf, "wb");
	if (!f) return;

	for (l = parent->subapps; l; l = l->next)
	  {
	     E_App *a;

	     a = l->data;
	     fprintf(f, "%s\n", ecore_file_get_file(a->path));
	  }
	/* Add the new files */
	for (l = files; l; l = l->next)
	  {
	     char *file;

	     file = l->data;
	     fprintf(f, "%s\n", ecore_file_get_file(file));
	  }
	fclose(f);
     }
}

EAPI void
e_app_remove(E_App *a)
{
   Evas_List *l;
   char buf[PATH_MAX];

   if (!a) return;
   E_OBJECT_CHECK(a);
   E_OBJECT_TYPE_CHECK(a, E_APP_TYPE);
   if (!a->parent) return;

   a->parent->subapps = evas_list_remove(a->parent->subapps, a);
   /* Check if this app is in a repository or in the parents dir */
   snprintf(buf, sizeof(buf), "%s/%s", a->parent->path, ecore_file_get_file(a->path));
   if (ecore_file_exists(buf))
     {
	/* Move to trash */
	snprintf(buf, sizeof(buf), "%s/%s", _e_apps_path_trash, ecore_file_get_file(a->path));
	ecore_file_mv(a->path, buf);
        _e_apps_every_app = evas_hash_del(_e_apps_every_app, a->path, a);
	evas_stringshare_del(a->path);
	a->path = evas_stringshare_add(buf);
	if (a->path)
           _e_apps_every_app = evas_hash_direct_add(_e_apps_every_app, a->path, a);
     }
   _e_app_save_order(a->parent);
   for (l = a->references; l; l = l->next)
     {
	E_App *a2;
	
	a2 = l->data;
	e_app_remove(a2);
     }
   _e_app_change(a, E_APP_DEL);
   a->parent = NULL;
   e_object_unref(E_OBJECT(a));
}

EAPI void
e_app_change_callback_add(void (*func) (void *data, E_App *a, E_App_Change ch), void *data)
{
   E_App_Callback *cb;
   
   cb = E_NEW(E_App_Callback, 1);
   cb->func = func;
   cb->data = data;
   _e_apps_change_callbacks = evas_list_append(_e_apps_change_callbacks, cb);
}

/* 
 * Delete the registered callback which has been registered with the data 
 * given data pointer. This function will return after the first match is
 * made.
 *
 * This will only delete the internal callback function reference. It will 
 * not delete the data. If the data or callback pointers can not be matched 
 * this function does nothing. 
 * 
 * @func pointer to function to be deleted 
 * @data pointer that was initialy registered with the add function
 */
EAPI void
e_app_change_callback_del(void (*func) (void *data, E_App *a, E_App_Change ch), void *data)
{
   Evas_List *l;
   
   for (l = _e_apps_change_callbacks; l; l = l->next)
     {
	E_App_Callback *cb;
	
	cb = l->data;
	if ((cb->func == func) && (cb->data == data))
	  {
	     if (_e_apps_callbacks_walking)
	       {
		  cb->delete_me = 1;
		  _e_apps_callbacks_delete_me = 1;
	       }
	     else
	       {
		  _e_apps_change_callbacks = evas_list_remove_list(_e_apps_change_callbacks, l);
		  free(cb);
	       }
	     return;
	  }
     }
}

/* Used by e_border and ibar. */
EAPI E_App *
e_app_launch_id_pid_find(int launch_id, pid_t pid)
{
   Evas_List *l, *ll;
   
   for (l = _e_apps_all->subapps; l; l = l->next)
     {
	E_App *a;

	a = l->data;
        E_OBJECT_CHECK_RETURN(a, NULL);
        E_OBJECT_TYPE_CHECK_RETURN(a, E_APP_TYPE, NULL);
	/* No need to fill up unfilled E_Apps during this scan. */
	for (ll = a->instances; ll; ll = ll->next)
	  {
	     E_App_Instance *ai;
	     
	     ai = ll->data;
	     if (((launch_id > 0) && (ai->launch_id > 0) && (ai->launch_id == launch_id)) ||
		 ((pid > 1) && (ai->exe) && (ecore_exe_pid_get(ai->exe) == pid)))
	       {
		  _e_apps_all->subapps = evas_list_remove_list(_e_apps_all->subapps, l);
		  _e_apps_all->subapps = evas_list_prepend(_e_apps_all->subapps, a);
		  return a;
	       }
	  }
     }
   return NULL;
}

#if CLEVER_BORDERS
static void
_e_apps_border_setup(Evas_Hash **non_glob, Evas_List **glob, const char *text, const char *path, char t)
{
   int is_glob = 0;

#if DEBUG
   printf("%c", t);
#endif
   if (glob)
     {
        /* Check if text is a glob.  This is a really simple check, it could be more complex. */
	if (strpbrk(text, "*?[]"))
	   is_glob = 1;
     }

   if (is_glob)
     {
        struct _E_App_Glob_List_Entry *entry;

        /* Put it into the glob list. */
        entry = E_NEW(struct _E_App_Glob_List_Entry, 1);
	if (entry)
	  {
	     entry->key = text;
	     entry->path = path;
	     (*glob) = evas_list_append(*glob, entry);
	     glob_count++;
	  }
     }
   else
     {
        Evas_List *entry = NULL;
	int found = 0;

        /* Put it into the list in the non_glob hash. */
        entry = evas_hash_find(*non_glob, text);
	if (entry)
	   found = 1;
	entry = evas_list_append(entry, path);
	if (entry)
	  {
             if (found)
	        evas_hash_modify(*non_glob, text, entry);
	     else
	        (*non_glob) = evas_hash_direct_add(*non_glob, text, entry);
	  }
     }
}


static void
_e_apps_winners_add(Evas_Hash **winners, const char *path, const char *name, int match_name)
{
   int *count = NULL;
   int found = 0, do_it = 0, addition = 1;

   /* Do the name batching magic if we need to. */
   if (match_name)
     {
        E_App *a;

        /* If class didn't match then we would not be here. */
	/* If there is no name in the E_App, it is implicitily a match. */
	/* Its a match if the E_App name and the border name are both NULL. */
	/* If a name glob was specified in the E_App, then try to match that with the border name. */
        addition = 2;
        a = e_app_path_find(path);
	if (a)
	   if ( (!a->win_name) || ((!name) && (!a->win_name)) || (e_util_glob_match(name, a->win_name)) )
              do_it = 1;
     }
   else
     do_it = 1;

   if (do_it)
     {
        count = evas_hash_find((*winners), path);
        if (count)
           found = 1;
        else
           count = calloc(1, sizeof(int));  // FIXME: not very efficient, allocate a bunch of them in one go and point into the next available one.

        if (count)
          {
             (*count) = (*count) + addition;
             if (found)
	        evas_hash_modify((*winners), path, count);
	     else
	        (*winners) = evas_hash_direct_add((*winners), path, count);
          }
     }
}

static void
_e_apps_winners_search(Evas_Hash *non_glob, Evas_List *glob, const char *text, const char *name, Evas_Hash **winners, int match_name)
{
   Evas_List *l, *entry = NULL;

   /* Check the non_glob list first. */
   entry = evas_hash_find(non_glob, text);
   if (entry)
     {
        for (l = entry; l; l = l->next)
          {
	     const char *path;

	     path = l->data;
             _e_apps_winners_add(winners, path, name, match_name);
	  }
     }
   /* Then check the glob lilst. */
   if (glob)
     {
        for (l = glob; l; l = l->next)
          {
             struct _E_App_Glob_List_Entry *glob;

	     glob = l->data;
	     if (e_util_glob_match(text, glob->key))
                _e_apps_winners_add(winners, glob->path, name, match_name);
          }
     }
}

static Evas_Bool
_e_apps_winners_hash_cb_check_free(Evas_Hash *hash, const char *key, void *data, void *fdata)
{
   struct _E_App_Glob_List_Winner *winner;
   int *count;

   count = data;
   winner = fdata;
#if DEBUG
   printf("%d %d %s\n", winner->ok, (*count), key);
#endif
   if ((*count) > winner->ok)
     {
        winner->path = key;
	winner->ok = (*count);
     }
   free(count);
   return 1;
}
#endif

/* Used by e_border and ibar. */
EAPI E_App *
e_app_border_find(E_Border *bd)
{
   Evas_List *l;
   int ok;
   E_App *a = NULL, *a_match = NULL, *clever_match = NULL;
   char *title;
   double begin, time, clever_time = 0.0;

   if ((!bd->client.icccm.name) && (!bd->client.icccm.class) &&
       (!bd->client.icccm.title) && (!bd->client.netwm.name) &&
       (!bd->client.icccm.window_role) && (!bd->client.icccm.command.argv))
     return NULL;

   title = bd->client.netwm.name;
   if (!title) title = bd->client.icccm.title;
   begin = ecore_time_get();
/* About the CLEVER_BORDERS code.
  
  ASSUMPTIONS:
    When E execs an E_App, it will fill the E_App first.
    All E_Apps are eventually filled in by an idler early on.
    There will be enough non glob win_things that the extra complexity will be 
    much faster than the current linear glob search through the list of E_Apps.
    The globs wont match so many E_Apps that the final check for highest count 
    wont take too much time.  It might be better to insert into a sorted by 
    count list, then just pull out the top one.

  PROBLEMS:
    Apps started early, but outside E, most likely from a terminal, may get 
    the wrong border.  This will be a result of the two pass part that is 
    designed to make sure the startup apps don't sit and wait for all the 
    E_Apps to be filled.  The same problem might hit all currently running 
    apps during a restart.

  DATA:
    There will be non-glob hash + glob list for each of win_class, win_title, 
    win_role, and win_name.
    There will also be a non-glob hash for exe.
    The non-glob hashs are keyed on the exe, win_class, win_title, win_role, or 
    win_name.
    They store a list of E_App paths that contain that exe, win_class, win_title, 
    win_role, or win_name.

  INIT:
    for each app
      put exe in the exe non-glob hash.  (key = exe, data = list of paths)
      if win_class, win_title, win_role, and win_name is not a glob
        look it up in the non-glob hash, add path to the list stored there
      else
        put it in the glob list

  FIND:
    2 passes (This speeds up startup programs, as the E_App should be filled.)
      first without calling e_app_fields_fill and skipping the unfilled ones
      if there is no winner
        only do the ones that need filling on the second pass
	Actually, this seems fast enough that I think we can get away with just 
	running it on all after the fill.

    on each pass
      look up client strings in non-glob hashes
        add contents of resulting list to temp hash (key = path, data = count of matches)
      go through glob lists
        if a glob matches, store path in the temp hash
      go through temp hash, the hash entry with the highest count wins
*/
#if CLEVER_BORDERS
{
   Evas_Hash *winners = NULL;
   struct _E_App_Glob_List_Winner winner;

   for (ok = 0; ok < 2; ok++)
     {
        winner.path = NULL;
        winner.ok = 0;
        if (ok)  /* Fill all E_Apps and try again on the second pass. */
	  {
#if DEBUG
             printf("SECOND PASS\n");
#endif
             for (l = _e_apps_all->subapps; l; l = l->next)
               {
	          a = l->data;
                  E_OBJECT_CHECK_RETURN(a, NULL);
                  E_OBJECT_TYPE_CHECK_RETURN(a, E_APP_TYPE, NULL);
                  if ((!a->idle_fill) && (!a->filled))
		     e_app_fields_fill(a, a->path);
	       }
	  }
        if ((bd->client.icccm.class))
           _e_apps_winners_search(_e_apps_border_ng_win_class, _e_apps_border_g_win_class, bd->client.icccm.class, bd->client.icccm.name, &winners, TRUE);
        if ((title))
           _e_apps_winners_search(_e_apps_border_ng_win_title, _e_apps_border_g_win_title, title, NULL, &winners, FALSE);
        if ((bd->client.icccm.window_role))
           _e_apps_winners_search(_e_apps_border_ng_win_role, _e_apps_border_g_win_role, bd->client.icccm.window_role, NULL, &winners, FALSE);
        /* We deliberately ignore the exe arguments when matching, even though ICCCM supplies them.
	 * Ask raster if you want to know why.
	 */
        if ((bd->client.icccm.command.argv) && (bd->client.icccm.command.argv[0]))
           _e_apps_winners_search(_e_apps_border_ng_exe, NULL, bd->client.icccm.command.argv[0], NULL, &winners, FALSE);

        evas_hash_foreach(winners, _e_apps_winners_hash_cb_check_free, &winner);
        if (winner.path)
	  {
              clever_match = e_app_path_find(winner.path);
	      if (clever_match)
	       {
		  if (winners) evas_hash_free(winners);
		  break;
	       }
	  }
        if (winners)
	  {
	     evas_hash_free(winners);
	     winners = NULL;
	  }
     }
}
   clever_time = ecore_time_get() - begin;
#endif
#if OLD_BORDERS
   for (l = _e_apps_all->subapps; l; l = l->next)
     {
	a = l->data;
        E_OBJECT_CHECK_RETURN(a, NULL);
        E_OBJECT_TYPE_CHECK_RETURN(a, E_APP_TYPE, NULL);
        if ((!a->idle_fill) && (!a->filled))   e_app_fields_fill(a, a->path);
	if (!a->filled)   continue;
	ok = 0;
	if ((a->win_name) || (a->win_class) || (a->win_title) || (a->win_role) || (a->exe))
	  {
	     if ((a->win_name) && (a->win_class) && 
		 (bd->client.icccm.name) && (bd->client.icccm.class))
	       {
		  if ((e_util_glob_match(bd->client.icccm.name, a->win_name)) &&
		      (e_util_glob_match(bd->client.icccm.class, a->win_class)))
		    ok += 2;
	       }
	     else if ((a->win_class) && (bd->client.icccm.class))
	       {
		  if (e_util_glob_match(bd->client.icccm.class, a->win_class))
		    ok += 2;
	       }
	     
	     if (//(!a->win_title) ||
		 ((a->win_title) && (title) && (e_util_glob_match(title, a->win_title))))
	       ok++;
	     if (//(!a->win_role) ||
		 ((a->win_role) && (bd->client.icccm.window_role) && (e_util_glob_match(bd->client.icccm.window_role, a->win_role))))
	       ok++;
	     if ((a->exe) && (bd->client.icccm.command.argv) && (bd->client.icccm.command.argv[0]))
	       {
/* a->exe is now already split at the space, so no need to do this.
 * I left it in in case that becomes false.
		  char *ts, *p;
		  
		  ts = alloca(strlen(a->exe) + 1);
		  strcpy(ts, a->exe);
		  p = ts;
		  while (*p)
		    {
		       if (isspace(*p))
			 {
			    *p = 0;
			    break;
			 }
		       p++;
		    }
		  if (!strcmp(ts, bd->client.icccm.command.argv[0]))
		    ok++;
*/
		  if (!strcmp(a->exe, bd->client.icccm.command.argv[0]))
		    ok++;
	       }
	  }
	if (ok > match)
	  {
	     match = ok;
	     a_match = a;
	     l_match = l;
	  }
     }
   if ((a_match) && (l_match))
     {
	_e_apps_all->subapps = evas_list_remove_list(_e_apps_all->subapps, l_match);
	_e_apps_all->subapps = evas_list_prepend(_e_apps_all->subapps, a_match);
     }
#else
   a_match = clever_match;
   clever_match = NULL;
#endif
   time = ecore_time_get() - begin;
   time -= clever_time;
   border_count++;
   border_time += time;
   border_clever_time += clever_time;
#if DEBUG
#if CLEVER_BORDERS && OLD_BORDERS
   printf("APP BORDER SCAN NEW - %2.6f, OLD - %2.6f (average %2.6f, %2.6f) FOUND %s AND %s\n",
          clever_time, time, border_clever_time / border_count, border_time / border_count, 
	  ((clever_match == NULL) ? "NOTHING" : clever_match->path), ((a_match == NULL) ? "NOTHING" : a_match->path));
#else
#if CLEVER_BORDERS
   printf("APP BORDER SCAN NEW - %2.6f (average %2.6f) FOUND %s\n",
          clever_time, border_clever_time / border_count, 
	  ((a_match == NULL) ? "NOTHING" : a_match->path));
#endif
#if OLD_BORDERS
   printf("APP BORDER SCAN OLD - %2.6f (average %2.6f) FOUND %s\n",
          time, border_time / border_count, 
	  ((a_match == NULL) ? "NOTHING" : a_match->path));
#endif
#endif
#endif
   return a_match;
}

/* Used by e_actions. */
EAPI E_App *
e_app_file_find(const char *file)
{
   Evas_List *l;
   
   if (!file) return NULL;

   for (l = _e_apps_all->subapps; l; l = l->next)
     {
	E_App *a;
	
	a = l->data;
        E_OBJECT_CHECK_RETURN(a, NULL);
        E_OBJECT_TYPE_CHECK_RETURN(a, E_APP_TYPE, NULL);
	if (a->path)
	  {
	     char *p;
	     
	     p = strrchr(a->path, '/');
	     if (p)
	       {
		  p++;
		  if (!strcmp(p, file))
		    {
		       _e_apps_all->subapps = evas_list_remove_list(_e_apps_all->subapps, l);
		       _e_apps_all->subapps = evas_list_prepend(_e_apps_all->subapps, a);
		       return a;
		    }
	       }
	  }
     }
   return NULL;
}

/* Used by e_apps. */
EAPI E_App *
e_app_path_find(const char *path)
{
   E_App *a = NULL;

   if ((path) && (_e_apps_every_app))
     a = evas_hash_find(_e_apps_every_app, path);
   return a;
}

/* Used by e_actions and e_exebuf. */
EAPI E_App *
e_app_name_find(const char *name)
{
   Evas_List *l;
   
   if (!name) return NULL;

   for (l = _e_apps_all->subapps; l; l = l->next)
     {
	E_App *a;
	
	a = l->data;
        E_OBJECT_CHECK_RETURN(a, NULL);
        E_OBJECT_TYPE_CHECK_RETURN(a, E_APP_TYPE, NULL);
        if ((!a->idle_fill) && (!a->filled))
	  e_app_fields_fill(a, a->path);
	if (!a->filled) continue;
	if (a->name)
	  {
	     if (!strcasecmp(a->name, name))
	       {
		  _e_apps_all->subapps = evas_list_remove_list(_e_apps_all->subapps, l);
		  _e_apps_all->subapps = evas_list_prepend(_e_apps_all->subapps, a);
		  return a;
	       }
	  }
     }
   return NULL;
}

/* Used by e_actions and e_exebuf. */
EAPI E_App *
e_app_generic_find(const char *generic)
{
   Evas_List *l;
   
   if (!generic) return NULL;

   for (l = _e_apps_all->subapps; l; l = l->next)
     {
	E_App *a;
	
	a = l->data;
        E_OBJECT_CHECK_RETURN(a, NULL);
        E_OBJECT_TYPE_CHECK_RETURN(a, E_APP_TYPE, NULL);
        if ((!a->idle_fill) && (!a->filled))
	  e_app_fields_fill(a, a->path);
	if (!a->filled) continue;
	if (a->generic)
	  {
	     if (!strcasecmp(a->generic, generic))
	       {
		  _e_apps_all->subapps = evas_list_remove_list(_e_apps_all->subapps, l);
		  _e_apps_all->subapps = evas_list_prepend(_e_apps_all->subapps, a);
		  return a;
	       }
	  }
     }
   return NULL;
}

/* Used by e_actions, e_exebuf, and e_zone. */
EAPI E_App *
e_app_exe_find(const char *exe)
{
   Evas_List *l;
#if CLEVER_BORDERS
   E_App *a = NULL;

   l = evas_hash_find(_e_apps_border_ng_exe, exe);
   if (l)
      a = e_app_path_find((char *) l->data);
   /* FIXME: Don't know if it's important to still be returning the least recently used E_App. */
   return a;
#else
   
   if (!exe) return NULL;

   for (l = _e_apps_all->subapps; l; l = l->next)
     {
	E_App *a;
	
	a = l->data;
        E_OBJECT_CHECK_RETURN(a, NULL);
        E_OBJECT_TYPE_CHECK_RETURN(a, E_APP_TYPE, NULL);
        if ((!a->idle_fill) && (!a->filled))
	  e_app_fields_fill(a, a->path);
	if (!a->filled) continue;
	if (a->exe)
	  {
	     if (!strcmp(a->exe, exe))
	       {
		  _e_apps_all->subapps = evas_list_remove_list(_e_apps_all->subapps, l);
		  _e_apps_all->subapps = evas_list_prepend(_e_apps_all->subapps, a);
		  return a;
	       }
	  }
     }
   return NULL;
#endif
}



/* Used e_exebuf. */
EAPI Evas_List *
e_app_name_glob_list(const char *name)
{
   Evas_List *l, *list = NULL;
   
   if (!name) return NULL;

   for (l = _e_apps_all->subapps; l; l = l->next)
     {
	E_App *a;
	
	a = l->data;
        E_OBJECT_CHECK_RETURN(a, NULL);
        E_OBJECT_TYPE_CHECK_RETURN(a, E_APP_TYPE, NULL);
        if ((!a->idle_fill) && (!a->filled))
	  e_app_fields_fill(a, a->path);
	if (!a->filled) continue;
	if (a->name)
	  {
	     if (e_util_glob_case_match(a->name, name))
	       list = evas_list_append(list, a);
	  }
     }
   return list;
}

/* Used e_exebuf. */
EAPI Evas_List *
e_app_generic_glob_list(const char *generic)
{
   Evas_List *l, *list = NULL;
   
   if (!generic) return NULL;

   for (l = _e_apps_all->subapps; l; l = l->next)
     {
	E_App *a;
	
	a = l->data;
        E_OBJECT_CHECK_RETURN(a, NULL);
        E_OBJECT_TYPE_CHECK_RETURN(a, E_APP_TYPE, NULL);
        if ((!a->idle_fill) && (!a->filled))
	  e_app_fields_fill(a, a->path);
	if (!a->filled) continue;
	if (a->generic)
	  {
	     if (e_util_glob_case_match(a->generic, generic))
	       list = evas_list_append(list, a);
	  }
     }
   return list;
}

/* Used e_exebuf. */
EAPI Evas_List *
e_app_exe_glob_list(const char *exe)
{
   Evas_List *l, *list = NULL;
   
   if (!exe) return NULL;

   for (l = _e_apps_all->subapps; l; l = l->next)
     {
	E_App *a;
	
	a = l->data;
        E_OBJECT_CHECK_RETURN(a, NULL);
        E_OBJECT_TYPE_CHECK_RETURN(a, E_APP_TYPE, NULL);
        if ((!a->idle_fill) && (!a->filled))
	  e_app_fields_fill(a, a->path);
	if (!a->filled) continue;
	if (a->exe)
	  {
	     if (e_util_glob_match(a->exe, exe))
	       list = evas_list_append(list, a);
	  }
     }
   return list;
}

/* Used e_exebuf. */
EAPI Evas_List *
e_app_comment_glob_list(const char *comment)
{
   Evas_List *l, *list = NULL;
   
   if (!comment) return NULL;

   for (l = _e_apps_all->subapps; l; l = l->next)
     {
	E_App *a;
	
	a = l->data;
        E_OBJECT_CHECK_RETURN(a, NULL);
        E_OBJECT_TYPE_CHECK_RETURN(a, E_APP_TYPE, NULL);
        if ((!a->idle_fill) && (!a->filled))
	  e_app_fields_fill(a, a->path);
	if (!a->filled)
	  continue;
	if (a->comment)
	  {
	     if (e_util_glob_case_match(a->comment, comment))
	       list = evas_list_append(list, a);
	  }
     }
   return list;
}

/* Used e_fm2. */
EAPI Evas_List *
e_app_mime_list(const char *mime)
{
   Evas_List *l, *list = NULL;
   
   if (!mime) return NULL;

   for (l = _e_apps_all->subapps; l; l = l->next)
     {
	E_App *a;
	
	a = l->data;
        E_OBJECT_CHECK_RETURN(a, NULL);
        E_OBJECT_TYPE_CHECK_RETURN(a, E_APP_TYPE, NULL);
        if ((!a->idle_fill) && (!a->filled))
	  e_app_fields_fill(a, a->path);
	if (!a->filled)
	  continue;
	if ((a->desktop) && (a->desktop->MimeTypes))
	  {
	     if (ecore_hash_get(a->desktop->MimeTypes, mime))
	       list = evas_list_append(list, a);
	  }
     }
   return list;
}


EAPI void
e_app_fields_fill(E_App *a, const char *path)
{
   const char *lang;
   
   /* get our current language */
   lang = e_intl_language_alias_get();
   
   /* if its "C" its the default - so drop it */
   if (!strcmp(lang, "C"))
     {
	lang = NULL;
     }
   if (!path) path = a->path;
   if (!path) return;

   if (_e_app_is_eapp(path))
     {   /* It's a .desktop file. */
	Ecore_Desktop *desktop;

	desktop = ecore_desktop_get(path, lang);
	if (desktop)
	  {
	     a->desktop = desktop;

#if DEBUG
             printf(".");
#endif
	     if (desktop->name)  a->name = evas_stringshare_add(desktop->name);
	     if (desktop->generic)  a->generic = evas_stringshare_add(desktop->generic);
	     if (desktop->comment)  a->comment = evas_stringshare_add(desktop->comment);
	     
	     if (desktop->exec)  a->exe = evas_stringshare_add(desktop->exec);
	     if (desktop->exec_params)  a->exe_params = evas_stringshare_add(desktop->exec_params);
	     if (desktop->icon)  a->icon = evas_stringshare_add(desktop->icon);
	     if (desktop->icon_theme)  a->icon_theme = evas_stringshare_add(desktop->icon_theme);
	     if (desktop->icon_class)  a->icon_class = evas_stringshare_add(desktop->icon_class);
	     if (desktop->icon_path)  a->icon_path = evas_stringshare_add(desktop->icon_path);
	     if (desktop->window_name)  a->win_name = evas_stringshare_add(desktop->window_name);
	     if (desktop->window_class)  a->win_class = evas_stringshare_add(desktop->window_class);
	     if (desktop->window_title)  a->win_title = evas_stringshare_add(desktop->window_title);
	     if (desktop->window_role)  a->win_role = evas_stringshare_add(desktop->window_role);
	     a->icon_time = desktop->icon_time;
	     a->startup_notify = desktop->startup;
	     a->wait_exit = desktop->wait_exit;
	     a->hard_icon = desktop->hard_icon;
	     a->dirty_icon = 0;
             a->icon_type = E_APP_ICON_UNKNOWN;

	     a->filled = 1;

//	   if (desktop->type)  a->type = evas_stringshare_add(desktop->type);
//	   if (desktop->categories)  a->categories = evas_stringshare_add(desktop->categories);
	  }
     }
#if CLEVER_BORDERS
   if (a->filled)
     {
        double begin, time;

        begin = ecore_time_get();

        if ((a->path) && (a->win_class))   _e_apps_border_setup(&_e_apps_border_ng_win_class, &_e_apps_border_g_win_class, a->win_class, a->path, 'c');
        if ((a->path) && (a->win_name))    _e_apps_border_setup(&_e_apps_border_ng_win_name,  &_e_apps_border_g_win_name,  a->win_name,  a->path, 'n');
        if ((a->path) && (a->win_title))   _e_apps_border_setup(&_e_apps_border_ng_win_title, &_e_apps_border_g_win_title, a->win_title, a->path, 't');
        if ((a->path) && (a->win_role))    _e_apps_border_setup(&_e_apps_border_ng_win_role,  &_e_apps_border_g_win_role,  a->win_role,  a->path, 'r');
        if ((a->path) && (a->exe))         _e_apps_border_setup(&_e_apps_border_ng_exe, NULL, a->exe, a->path, 'e');

        time = ecore_time_get() - begin;
        border_setup_count++;
        border_setup_time += time;
     }
#endif
}

EAPI void
e_app_fields_save(E_App *a)
{
   char buf[PATH_MAX];
   const char *ext = NULL;
   int new_eap = 0;

   E_OBJECT_CHECK(a);
   E_OBJECT_TYPE_CHECK(a, E_APP_TYPE);
   /* Check if it's a new one that has not been saved yet. */
   if (a->path)
      ext = ecore_file_get_file(a->path);
   if ( (!a->path) || ((strncmp(ext, "_new_app_", 9) == 0) && (!ecore_file_exists(a->path))) )
      {
         snprintf(buf, sizeof(buf), "%s/%s.desktop", _e_apps_path_all, a->name);
	 if (a->path)  evas_stringshare_del(a->path);
	 a->path = evas_stringshare_add(buf);
      }
   if (!a->path)  return;
   /* This still lets old ones that are not in all to be saved, but new ones are forced to be in all. */
   if (!ecore_file_exists(a->path))
      {
         /* Force it to be in all. */
         snprintf(buf, sizeof(buf), "%s/%s", _e_apps_path_all, ecore_file_get_file(a->path));
	 if (a->path)  evas_stringshare_del(a->path);
	 a->path = evas_stringshare_add(buf);
      }
   if (!a->path)  return;
   if (ecore_file_exists(a->path))
      _e_apps_every_app = evas_hash_del(_e_apps_every_app, a->path, a);
   else
      new_eap = 1;

   if (_e_app_is_eapp(a->path))
      {   /* It's a .desktop file. */
         Ecore_Desktop *desktop;
	 int created = 0;

         desktop = ecore_desktop_get(a->path, NULL);
	 if (!desktop)
	    {
	       desktop = E_NEW(Ecore_Desktop, 1);
	       desktop->original_path = strdup(a->path);
	       created = 1;
	    }
	 if (desktop)
	    {
	       desktop->name = (char *) a->name;
	       desktop->generic = (char *) a->generic;
	       desktop->comment = (char *) a->comment;

	       desktop->exec = (char *) a->exe;
	       desktop->exec_params = (char *) a->exe_params;
	       desktop->icon = (char *) a->icon;
	       desktop->icon_theme = (char *) a->icon_theme;
	       desktop->icon_class = (char *) a->icon_class;
               desktop->icon_path = (char *) a->icon_path;
	       desktop->window_name = (char *) a->win_name;
	       desktop->window_class = (char *) a->win_class;
	       desktop->window_title = (char *) a->win_title;
	       desktop->window_role = (char *) a->win_role;
	       desktop->icon_time = a->icon_time;
	       desktop->hard_icon = a->hard_icon;
	       desktop->startup = a->startup_notify;
	       desktop->wait_exit = a->wait_exit;

               desktop->type = "Application";
//               desktop.categories = a->categories;

               ecore_desktop_save(desktop);
               a->dirty_icon = 0;
	       if (created)
	          E_FREE(desktop);
	    }
      }

   _e_apps_every_app = evas_hash_direct_add(_e_apps_every_app, a->path, a);
   if (new_eap)
      {
         /* Careful, if this is being created from the border icon, this E_App is already part of the border. */
         a->parent = _e_apps_all;
         _e_apps_all->subapps = evas_list_append(_e_apps_all->subapps, a);
         e_object_ref(E_OBJECT(a));
	 /* FIXME: we need to copy and reference this since it is in the repository. */
	 _e_app_change(a, E_APP_ADD);
      }
   else
      _e_app_fields_save_others(a);
}

static void
_e_app_fields_save_others(E_App *a)
{
   Evas_List *l;

   for (l = a->references; l; l = l->next)
     {
        E_App *a2;
	     
	a2 = l->data;
	if (_e_app_copy(a2, a)) _e_app_change(a2, E_APP_CHANGE);
     }
   if (a->orig)
     {
	E_App *a2;
	     
	a2 = a->orig;
	if (_e_app_copy(a2, a)) _e_app_change(a2, E_APP_CHANGE);
     }
   _e_app_change(a, E_APP_CHANGE);
   if (a->parent)
     {
	_e_app_change(a->parent, E_APP_CHANGE);
	/* I don't think we need to rescan.
	 * A) we should be changing all the relevant stuff here.
	 * B) monitoring will catch other changes.
	 * C) it's a waste of time.
	_e_app_subdir_rescan(a->parent);
	 */
     }
}


EAPI void
e_app_fields_empty(E_App *a)
{
#if DEBUG
   printf("-");
#endif
   if (a->name) evas_stringshare_del(a->name);
   if (a->generic) evas_stringshare_del(a->generic);
   if (a->comment) evas_stringshare_del(a->comment);
   if (a->exe) evas_stringshare_del(a->exe);
   if (a->exe_params) evas_stringshare_del(a->exe_params);
   if (a->icon_theme) evas_stringshare_del(a->icon_theme);
   if (a->icon) evas_stringshare_del(a->icon);
   if (a->icon_class) evas_stringshare_del(a->icon_class);
   if (a->icon_path) evas_stringshare_del(a->icon_path);
   if (a->win_name) evas_stringshare_del(a->win_name);
   if (a->win_class) evas_stringshare_del(a->win_class);
   if (a->win_title) evas_stringshare_del(a->win_title);
   if (a->win_role) evas_stringshare_del(a->win_role);
   a->desktop = NULL;
   a->name = NULL;
   a->generic = NULL;
   a->comment = NULL;
   a->exe = NULL;
   a->exe_params = NULL;
   a->icon_theme = NULL;
   a->icon = NULL;
   a->icon_class = NULL;
   a->icon_path = NULL;
   a->win_name = NULL;
   a->win_class = NULL;
   a->win_title = NULL;
   a->win_role = NULL;
   a->dirty_icon = 0;
   a->hard_icon = 0;
   a->icon_type = E_APP_ICON_UNKNOWN;
   a->filled = 0;
   a->idle_fill = 0;
   a->idle_icon = 0;
}

EAPI Ecore_List *
e_app_dir_file_list_get(E_App *a)
{
   Ecore_List *files, *files2;
   char *file;
   FILE *f;
   char buf[PATH_MAX];
   
   snprintf(buf, sizeof(buf), "%s/.order", a->path);
//   printf("APX: LOAD %s\n", buf);
   files = ecore_file_ls(a->path);
   f = fopen(buf, "rb");
   if (f)
     {
	files2 = ecore_list_new();
	ecore_list_set_free_cb(files2, free);
	while (fgets(buf, sizeof(buf), f))
	  {
	     int len;

	     len = strlen(buf);
	     if (len > 0)
	       {
		  if (buf[len - 1] == '\n')
		    {
		       buf[len - 1] = 0;
		       len--;
		    }
		  if (len > 0)
		    {
		       if (files)
			 {
			    ecore_list_goto_first(files);
			    while ((file = ecore_list_current(files)))
			      {
				 if (!strcmp(buf, file))
				   {
				      ecore_list_remove(files);
				      free(file);
				      break;
				   }
				 ecore_list_next(files);
			      }
			 }
		       ecore_list_append(files2, strdup(buf));
		    }
	       }
	  }
	fclose(f);
     }
   else
     {
	files2 = files;
	files = NULL;
     }
   if (files)
     {
	ecore_list_goto_first(files);
	while ((file = ecore_list_next(files)))
	  {
	     if (ecore_file_get_file(file)[0] != '.')
	       ecore_list_append(files2, strdup(file));
	  }
	ecore_list_destroy(files);
     }
   files = files2;
   if (files)
     ecore_list_goto_first(files);
   return files;
}

EAPI int
e_app_valid_exe_get(E_App *a)
{
   char *exe;
   int ok = 1;

   if (!a->exe) return 0;
   exe = ecore_file_app_exe_get(a->exe);
   if (!exe) return 0;
   ok = ecore_file_app_installed(exe);
   free(exe);
   return ok;
}

static E_App_Icon_Type
_e_app_icon_type(E_App *a)
{
   if (a->icon_type == E_APP_ICON_UNKNOWN)
     {
        int theme_match = 0;

        /* If the icon was hard coded into the .desktop files Icon field, then theming doesn't matter. */
        if (a->hard_icon)
           theme_match = 1;
        else if ((e_config->icon_theme == NULL) && (a->icon_theme == NULL))  /* Check to see if the icon theme is different. */
           theme_match = 1;
        else if ((e_config->icon_theme) && (a->icon_theme) && (strcmp(e_config->icon_theme, a->icon_theme) == 0))
           theme_match = 1;

        /* Check if we already know the icon path. */
        if ((theme_match) && (a->icon_path) && (a->icon_path[0] != 0))
          {
	     a->icon_type = E_APP_ICON_PATH;
#if DEBUG
             printf("P");
#endif
          }
        else
          {
             /* Check the theme for icons. */
             if (e_util_edje_icon_list_check(a->icon_class))
	       {
	          a->icon_type = E_APP_ICON_CLASS;
#if DEBUG
	          printf("C");
#endif
	       }
             else
               {
                  if (edje_file_group_exists(a->path, "icon"))
	            {
	               a->icon_type = E_APP_ICON_GROUP;
#if DEBUG
	               printf("G");
#endif
	            }
                  else /* If that fails, then this might be an FDO icon. */
                    {
                       if (a->icon_class)
                         {
                            char *v = NULL;

                    	    v = (char *)ecore_desktop_icon_find(a->icon_class, "64x64", e_config->icon_theme);
                    	    if (v)
                    	      {
                                 if (a->icon_path) evas_stringshare_del(a->icon_path);
                    	         a->icon_path = evas_stringshare_add(v);
                    	         if (e_config->icon_theme)
                    	           {
                                      if (a->icon_theme) evas_stringshare_del(a->icon_theme);
                    		      a->icon_theme = evas_stringshare_add(e_config->icon_theme);
                    	           }
                    	         a->dirty_icon = 1;
                    	         free(v);

				 /* Copy the new found icon data to the original. */
				 _e_app_fields_save_others(a);
                    	      }
                          }
                    }

                  if (a->icon_path)
	            {
	               a->icon_type = E_APP_ICON_FDO;
#if DEBUG
	               printf("F");
#endif
	            }
	          else
	            {
	               a->icon_type = E_APP_ICON_NONE;
#if DEBUG
                       printf("N");
#endif
	            }
               }
          }
     }

   return a->icon_type;
}

EAPI Evas_Object *
e_app_icon_add(E_App *a, Evas *evas)
{
   Evas_Object *o = NULL;
   E_App_Icon_Type type;

   if (a->orig)
      a = a->orig;

#if DEBUG
printf("e_app_icon_add(%s)   %s   %s   %s\n", a->path, a->icon_class, e_config->icon_theme, a->icon_path);
#endif

   type = _e_app_icon_type(a);
   switch (type)
     {
        case E_APP_ICON_CLASS   : o = edje_object_add(evas);  e_util_edje_icon_list_set(o, a->icon_class); break;
        case E_APP_ICON_GROUP   : o = edje_object_add(evas);  edje_object_file_set(o, a->path, "icon");    break;
        case E_APP_ICON_PATH    :
        case E_APP_ICON_FDO     : 
          {
             char *ext;

             /* Be paranoid. */
             if (a->icon_path)
	       {
                  o = e_icon_add(evas);
                  ext = strrchr(a->icon_path, '.');
                  if (ext)
                     {
                        if (strcmp(ext, ".edj") == 0)
                           e_icon_file_edje_set(o, a->icon_path, "icon");
                        else
                           e_icon_file_set(o, a->icon_path);
                     }
                  else
                     e_icon_file_set(o, a->icon_path);
                  e_icon_fill_inside_set(o, 1);
	       }
	     break;
          }

        case E_APP_ICON_UNKNOWN :
        case E_APP_ICON_NONE    :
	default :
	   break;
     }
   return o;
}

/* Search order? -
 *
 * fixed path to icon
 * an .edj icon in ~/.e/e/icons
 * icon_class in theme
 * icon from a->path in theme
 * FDO search for icon_class
 */

EAPI void
e_app_icon_add_to_menu_item(E_App *a, E_Menu_Item *mi)
{
   E_App_Icon_Type type;

   if (a->orig)
      a = a->orig;
   mi->app = a;

#if DEBUG
printf("e_app_icon_add_to_menu_item(%s)   %s   %s   %s\n", a->path, a->icon_class, e_config->icon_theme, a->icon_path);
#endif

   /* e_menu_item_icon_edje_set() just tucks away the params, the actual call to edje_object_file_set() happens later. */
   /* e_menu_item_icon_file_set() just tucks away the params, the actual call to e_icon_add() happens later. */
   type = _e_app_icon_type(a);
   switch (type)
     {
        case E_APP_ICON_CLASS   : e_util_menu_item_edje_icon_list_set(mi, a->icon_class);  break;
        case E_APP_ICON_GROUP   : e_menu_item_icon_edje_set(mi, a->path, "icon");  break;
        case E_APP_ICON_PATH    :
        case E_APP_ICON_FDO     : 
          {
             char *ext;

             /* Be paranoid. */
             if (a->icon_path)
	       {
                  ext = strrchr(a->icon_path, '.');
                  if (ext)
                     {
                         if (strcmp(ext, ".edj") == 0)
                            e_menu_item_icon_edje_set(mi, a->icon_path, "icon");
                         else
                            e_menu_item_icon_file_set(mi, a->icon_path);
                     }
                  else
                     e_menu_item_icon_file_set(mi, a->icon_path);
	       }
	     break;
          }

        case E_APP_ICON_UNKNOWN :
        case E_APP_ICON_NONE    :
	default :
	   break;
     }
}


/* local subsystem functions */

static void
_e_app_free(E_App *a)
{
   if (a->path)
     _e_apps_every_app = evas_hash_del(_e_apps_every_app, a->path, a);

   /* FIXME: natsy - but trying to fix a bug */
   /* somehow apps for "startup" end up in subapps of _e_apps_all and
    * when thet startup app aprent is freed the subapps of the startup are
    * freed but the list entries for _e_apps_all->subapps is still there
    * and so u have a nasty dangling pointer to garbage memory
    */
//   if (_e_apps_all)
//     _e_apps_all->subapps = evas_list_remove(_e_apps_all->subapps, a);
   /* END FIXME */
   
   while (evas_list_find(_e_apps_start_pending, a))
     _e_apps_start_pending = evas_list_remove(_e_apps_start_pending, a);
   if (a->orig)
     {
	while (a->instances)
	  {
	     E_App_Instance *inst;
	     
	     inst = a->instances->data;
	     inst->app = a->orig;
	     a->orig->instances = evas_list_append(a->orig->instances, inst);
	     a->instances = evas_list_remove_list(a->instances, a->instances);
	     _e_apps_start_pending = evas_list_append(_e_apps_start_pending, a->orig);
	  }
	/* If this is a copy, it shouldn't have any references! */
	if (a->references)
	  printf("BUG: A eapp copy shouldn't have any references!\n");
	if (a->parent)
	  {
	     a->parent->subapps = evas_list_remove(a->parent->subapps, a);
	  }
	a->orig->references = evas_list_remove(a->orig->references, a);
	e_object_unref(E_OBJECT(a->orig));
	free(a);
     }
   else
     {
	while (a->instances)
	  {
	     E_App_Instance *inst;

	     inst = a->instances->data;
	     if (inst->expire_timer)
	       {
		  ecore_timer_del(inst->expire_timer);
		  inst->expire_timer = NULL;
	       }
	     if (inst->exe)
	       {
		  ecore_exe_tag_set(inst->exe, NULL);
//		  ecore_exe_free(inst->exe);
		  inst->exe = NULL;
	       }
	     free(inst);
	     a->instances = evas_list_remove_list(a->instances, a->instances);
	  }
	while (a->subapps)
	  {
	     E_App *a2;

	     a2 = a->subapps->data;
	     a->subapps = evas_list_remove_list(a->subapps, a->subapps);
	     /* remove us as the parent */
	     a2->parent = NULL;
	     /* unref the child so it will be deleted too */
	     e_object_unref(E_OBJECT(a2));
	  }
	/* If this is an original, it wont be deleted until all references
	 * are gone */
	while (a->references)
	  {
	     E_App *a2;
	     
	     a2 = a->references->data;
	     a2->orig = NULL;
	     a->references = evas_list_remove_list(a->references, a->references);
	     printf("BUG: An original eapp shouldn't have any references when freed! %d\n", evas_list_count(a->references));
	  }

	if (a->parent)
	  a->parent->subapps = evas_list_remove(a->parent->subapps, a);
	if (a->monitor)
	  ecore_file_monitor_del(a->monitor);
	e_app_fields_empty(a);
	if (a->path)
	  evas_stringshare_del(a->path);
	/* Just going that little extra paranoid, coz rephorm got a strange seggie. */
	a->monitor = NULL;
	a->path = NULL;
	free(a);
     }
}

/* Used by _e_app_cb_monitor and _e_app_subdir_rescan */ 
static E_App *
_e_app_subapp_file_find(E_App *a, const char *file)
{
   Evas_List *l;
   
   for (l = a->subapps; l; l = l->next)
     {
	E_App *a2;
	
	a2 = l->data;
        E_OBJECT_CHECK_RETURN(a2, NULL);
        E_OBJECT_TYPE_CHECK_RETURN(a2, E_APP_TYPE, NULL);
	if ((a2->deleted) || ((a2->orig) && (a2->orig->deleted))) continue;
	if (!strcmp(ecore_file_get_file(a2->path), ecore_file_get_file(file))) return a2;
     }
   return NULL;
}

static void
_e_app_change(E_App *a, E_App_Change ch)
{
   Evas_List *l;

#if DEBUG
   if (ch == E_APP_DEL)
     printf("APP_DEL %s\n", a->path);
   if (ch == E_APP_CHANGE)
     printf("APP_CHANGE %p %s\n", a, a->path);
   if (ch == E_APP_ADD)
     printf("APP_ADD %s\n", a->path);
#endif
   _e_apps_callbacks_walking = 1;
   for (l = _e_apps_change_callbacks; l; l = l->next)
     {
	E_App_Callback *cb;

	cb = l->data;
	if (!cb->delete_me) cb->func(cb->data, a, ch);
     }
   _e_apps_callbacks_walking = 0;
   if (_e_apps_callbacks_delete_me)
     {
	for (l = _e_apps_change_callbacks; l;)
	  {
	     E_App_Callback *cb;
	     Evas_List *pl;
	     
	     cb = l->data;
	     pl = l;
	     l = l->next;
	     if (cb->delete_me)
	       {
		  _e_apps_change_callbacks = evas_list_remove_list(_e_apps_change_callbacks, pl);
		  free(cb);
	       }
	  }
	_e_apps_callbacks_delete_me = 0;
     }
}

static void
_e_app_cb_monitor(void *data, Ecore_File_Monitor *em,
		  Ecore_File_Event event, const char *path)
{
   E_App     *app;
   char      *file;
   Evas_List *l;
   
   app = data;
   if ((!app) || (app->deleted))
     {
	printf("Event on a deleted eapp\n");
	return;
     }

   /* If this dir isn't scanned yet, no need to report changes! */
   if (!app->scanned)
     return;

#if DEBUG
   _e_app_print(path, event);
#endif

//   printf("APX: CHG %s\n", path);
   file = (char *)ecore_file_get_file(path);
   if (!strcmp(file, ".order"))
     {
	if ((event == ECORE_FILE_EVENT_CREATED_FILE) ||
	    (event == ECORE_FILE_EVENT_DELETED_FILE) ||
	    (event == ECORE_FILE_EVENT_MODIFIED))
	  {
	     _e_app_subdir_rescan(app);
	  }
	else
	  {
	     printf("BUG: Weird event for .order: %d\n", event);
	  }
     }
   else if (!strcmp(file, ".directory"))
     {
	if ((event == ECORE_FILE_EVENT_CREATED_FILE) ||
	    (event == ECORE_FILE_EVENT_MODIFIED))
	  {
	     e_app_fields_empty(app);
	     e_app_fields_fill(app, path);
	     if (app->filled)
	       {
		  _e_app_change(app, E_APP_CHANGE);
		  for (l = app->references; l; l = l->next)
		    {
		       E_App *a2;
		       
		       a2 = l->data;
		       if (_e_app_copy(a2, app)) _e_app_change(a2, E_APP_CHANGE);
		    }
	       }
	  }
	else if (event == ECORE_FILE_EVENT_DELETED_FILE)
	  {
	     e_app_fields_empty(app);
	     app->name = evas_stringshare_add(ecore_file_get_file(app->path));
	  }
	else
	  {
	     printf("BUG: Weird event for .directory: %d\n", event);
	  }
     }
   else
     {
	if (event == ECORE_FILE_EVENT_MODIFIED)
	  {
	     E_App *a;

	     a = _e_app_subapp_file_find(app, file);
	     if (a)
	       {
		  Evas_List *l;

		  e_app_fields_empty(a);
		  e_app_fields_fill(a, path);
		  if (a->filled)
		    {
		       _e_app_change(a, E_APP_CHANGE);
		       for (l = a->references; l; l = l->next)
			 {
			    E_App *a2;

			    a2 = l->data;
			    if (_e_app_copy(a2, a))
			      _e_app_change(a2, E_APP_CHANGE);
			 }
		       _e_app_subdir_rescan(app);
		    }
	       }
	  }
	else if ((event == ECORE_FILE_EVENT_CREATED_FILE) ||
		 (event == ECORE_FILE_EVENT_CREATED_DIRECTORY))
	  {
	     _e_app_subdir_rescan(app);
	     /* If this is the all app, check if someone wants to reference this app */
	     if (app == _e_apps_all)
	       _e_app_check_order(file);

	  }
	else if (event == ECORE_FILE_EVENT_DELETED_FILE)
	  {
	     E_App *a;
	     Evas_List *l;

	     a = _e_app_subapp_file_find(app, file);
	     if (a)
	       {
		  a->deleted = 1;
		  for (l = a->references; l;)
		    {
		       E_App *a2;

		       a2 = l->data;
		       l = l->next;
		       if (a2->parent)
			 _e_app_subdir_rescan(a2->parent);
		    }
		  _e_app_subdir_rescan(app);
	       }
	  }
	else if (event == ECORE_FILE_EVENT_DELETED_SELF)
	  {
	     Evas_List *l;

	     app->deleted = 1;
	     for (l = app->references; l;)
	       {
		  E_App *a2;

		  a2 = l->data;
		  l = l->next;
		  if (a2->parent)
		    _e_app_subdir_rescan(a2->parent);
	       }
	     if (app->parent)
	       _e_app_subdir_rescan(app->parent);
	     else
	       {
		  /* A main e_app has been deleted!
		   * We don't unref this, the code which holds this
		   * eapp must do it. */
		  _e_app_change(app, E_APP_DEL);
	       }
	  }
     }
}

static void
_e_app_subdir_rescan(E_App *app)
{
   Ecore_List        *files;
   Evas_List         *subapps = NULL, *changes = NULL, *l, *l2;
   E_App_Change_Info *ch;
   char               buf[PATH_MAX];
   char              *s;

   files = e_app_dir_file_list_get(app);
   if (files)
     {
	while ((s = ecore_list_next(files)))
	  {
	     E_App *a2;

	     a2 = _e_app_subapp_file_find(app, s);
	     if (a2)
	       {
		  subapps = evas_list_append(subapps, a2);
		  e_object_ref(E_OBJECT(a2));
	       }
	     else
	       {
		  /* If we still haven't found it, it is new! */
                  _e_app_resolve_file_name(buf, sizeof(buf), app->path, s);
		  a2 = e_app_new(buf, 1);
		  if (a2)
		    {
		       a2->parent = app;
		       ch = E_NEW(E_App_Change_Info, 1);
		       ch->app = a2;
		       ch->change = E_APP_ADD;
		       e_object_ref(E_OBJECT(ch->app));
		       changes = evas_list_append(changes, ch);

		       subapps = evas_list_append(subapps, a2);
		       e_object_ref(E_OBJECT(a2));
		    }
	       }
	  }
	ecore_list_destroy(files);
     }
   for (l = app->subapps; l; l = l->next)
     {
	E_App *a2;

	a2 = l->data;
	for (l2 = subapps; l2; l2 = l2->next)
	  if (l->data == l2->data)
	    {
	       /* We still have this app */
               e_object_unref(E_OBJECT(a2));
	       a2 = NULL;
	       break;
	    }
	if (a2)
	  {
	     a2->deleted = 1;
	     ch = E_NEW(E_App_Change_Info, 1);
	     ch->app = a2;
	     ch->change = E_APP_DEL;
	     e_object_ref(E_OBJECT(ch->app));
	     changes = evas_list_append(changes, ch);
             e_object_unref(E_OBJECT(a2));
	  }
     }
   /* FIXME: We only need to tell about order changes if there are! */
   evas_list_free(app->subapps);
   app->subapps = subapps;
   ch = E_NEW(E_App_Change_Info, 1);
   ch->app = app;
   ch->change = E_APP_ORDER;
   e_object_ref(E_OBJECT(ch->app));
   changes = evas_list_append(changes, ch);

   for (l = changes; l; l = l->next)
     {
	ch = l->data;
	_e_app_change(ch->app, ch->change);
	e_object_unref(E_OBJECT(ch->app));
	free(ch);
     }
   evas_list_free(changes);
}

static int
_e_app_is_eapp(const char *path)
{
   char *p;

   if (!path)
     return 0;
   p = strrchr(path, '.');
   if (!p)
     return 0;

   p++;
   if ((strcasecmp(p, "desktop") != 0) && (strcasecmp(p, "directory") != 0))
     return 0;

   return 1;
}

static int
_e_app_copy(E_App *dst, E_App *src)
{
   if (src->deleted)
     {
	printf("BUG: This app is deleted, can't make a copy: %s\n", src->path);
	return 0;
     }
   if (!_e_app_is_eapp(src->path))
     {
	printf("BUG: The app isn't an eapp: %s\n", src->path);
	return 0;
     }

   dst->orig = src;
   dst->desktop = src->desktop;

   dst->path = src->path;
   dst->name = src->name;
   dst->generic = src->generic;
   dst->comment = src->comment;
   dst->exe = src->exe;
   dst->exe_params = src->exe_params;
   dst->win_name = src->win_name;
   dst->win_class = src->win_class;
   dst->win_title = src->win_title;
   dst->win_role = src->win_role;
   dst->icon_theme = src->icon_theme;
   dst->icon = src->icon;
   dst->icon_class = src->icon_class;
   dst->icon_path = src->icon_path;
   dst->startup_notify = src->startup_notify;
   dst->wait_exit = src->wait_exit;
   dst->icon_time = src->icon_time;
   dst->starting = src->starting;
   dst->scanned = src->scanned;
   dst->icon_type = src->icon_type;
   dst->dirty_icon = src->dirty_icon;
   dst->hard_icon = src->hard_icon;
   dst->filled = src->filled;
   dst->idle_fill = src->idle_fill;
   dst->idle_icon = src->idle_icon;

   return 1;
}

static void
_e_app_save_order(E_App *app)
{
   FILE *f;
   char buf[PATH_MAX];
   Evas_List *l;
   
   if (!app) return;

   snprintf(buf, sizeof(buf), "%s/.order", app->path);
   f = fopen(buf, "wb");
   if (!f) return;

   for (l = app->subapps; l; l = l->next)
     {
	E_App *a;

	a = l->data;
	fprintf(f, "%s\n", ecore_file_get_file(a->path));
     }
   fclose(f);
}

static int
_e_apps_cb_exit(void *data, int type, void *event)
{
   Ecore_Exe_Event_Del *ev;
   E_App_Instance *ai;
   E_App *a;
   Evas_List *l;

   ev = event;
   if (!ev->exe) return 1;
   if (!(ecore_exe_tag_get(ev->exe) && 
	 (!strcmp(ecore_exe_tag_get(ev->exe), "E/app")))) return 1;
   ai = ecore_exe_data_get(ev->exe);
   if (!ai) return 1;
   a = ai->app;
   if (!a) return 1;

   /* /bin/sh uses this if cmd not found */
   if ((ev->exited) &&
       ((ev->exit_code == 127) || (ev->exit_code == 255)))
     {
	E_Dialog *dia;
	
	dia = e_dialog_new(e_container_current_get(e_manager_current_get()), "E", "_app_run_error_dialog");
	if (dia)
	  {
	     char buf[4096];
	     
	     e_dialog_title_set(dia, _("Application run error"));
	     snprintf(buf, sizeof(buf),
		      _("Enlightenment was unable to run the application:<br>"
			"<br>"
			"%s<br>"
			"<br>"
			"The application failed to start."),
		      a->exe);
	     e_dialog_text_set(dia, buf);
//	     e_dialog_icon_set(dia, "enlightenment/error", 64);
	     e_dialog_button_add(dia, _("OK"), NULL, NULL, NULL);
	     e_dialog_button_focus_num(dia, 1);
	     e_win_centered_set(dia->win, 1);
	     e_dialog_show(dia);
	  }
     }
   /* Let's hope that everyhing returns this properly. */
   else if (!((ev->exited) && (ev->exit_code == EXIT_SUCCESS))) 
     {   /* Show the error dialog with details from the exe. */
	E_App_Autopsy *aut;
	
	aut = E_NEW(E_App_Autopsy, 1);
	aut->app = a;
	aut->del = *ev;
	aut->error = ecore_exe_event_data_get(ai->exe, ECORE_EXE_PIPE_ERROR);
	aut->read = ecore_exe_event_data_get(ai->exe, ECORE_EXE_PIPE_READ);
	e_app_error_dialog(NULL, aut);
     }
   a->instances = evas_list_remove(a->instances, ai);
   ai->exe = NULL;
   if (ai->expire_timer) ecore_timer_del(ai->expire_timer);
   free(ai);
   _e_apps_start_pending = evas_list_remove(_e_apps_start_pending, a);
   for (l = a->references; l; l = l->next)
     {
	E_App *a2;
	
	a2 = l->data;
	_e_app_change(a2, E_APP_EXIT);
     }
   _e_app_change(a, E_APP_EXIT);
   return 1;
}

static int
_e_app_cb_event_border_add(void *data, int type, void *event)
{
   E_Event_Border_Add *ev;
   Evas_List *l, *ll, *removes = NULL;
   E_App *a;
   E_App_Instance *inst;
   
   ev = event;
   if (ev->border->client.netwm.startup_id <= 0) return 1;
   for (l = _e_apps_start_pending; l; l = l->next)
     {
	a = l->data;
	for (ll = a->instances; ll; ll = ll->next)
	  {
	     inst = ll->data;
	     if (inst->launch_id == ev->border->client.netwm.startup_id)
	       {
		  if (inst->expire_timer)
		    {
		       ecore_timer_del(inst->expire_timer);
		       inst->expire_timer = NULL;
		    }
		  removes = evas_list_append(removes, a);
		  e_object_ref(E_OBJECT(a));
		  break;
	       }
	  }
     }
   while (removes)
     {
	a = removes->data;
	for (l = a->references; l; l = l->next)
	  {
	     E_App *a2;
	     
	     a2 = l->data;
	     _e_app_change(a2, E_APP_READY);
	  }
        _e_app_change(a, E_APP_READY);
	_e_apps_start_pending = evas_list_remove(_e_apps_start_pending, a);
        e_object_unref(E_OBJECT(a));
	removes = evas_list_remove_list(removes, removes);
     }
   return 1;
}

static int
_e_app_cb_expire_timer(void *data)
{
   E_App_Instance *inst;
   E_App *a;
   Evas_List *l;
   
   inst = data;
   a = inst->app;
   _e_apps_start_pending = evas_list_remove(_e_apps_start_pending, a);
   inst->expire_timer = NULL;
   for (l = a->references; l; l = l->next)
     {
	E_App *a2;
	
	a2 = l->data;
	_e_app_change(a2, E_APP_READY_EXPIRE);
     }
   _e_app_change(a, E_APP_READY_EXPIRE);
   return 0;
}

#if DEBUG
static void
_e_app_print(const char *path, Ecore_File_Event event)
{
   switch (event)
     {
      case ECORE_FILE_EVENT_NONE:
	 printf("E_App none: %s\n", path);
	 break;
      case ECORE_FILE_EVENT_CREATED_FILE:
	 printf("E_App created file: %s\n", path);
	 break;
      case ECORE_FILE_EVENT_CREATED_DIRECTORY:
	 printf("E_App created directory: %s\n", path);
	 break;
      case ECORE_FILE_EVENT_DELETED_FILE:
	 printf("E_App deleted file: %s\n", path);
	 break;
      case ECORE_FILE_EVENT_DELETED_DIRECTORY:
	 printf("E_App deleted directory: %s\n", path);
	 break;
      case ECORE_FILE_EVENT_DELETED_SELF:
	 printf("E_App deleted self: %s\n", path);
	 break;
      case ECORE_FILE_EVENT_MODIFIED:
	 printf("E_App modified: %s\n", path);
	 break;
     }
}
#endif

static void
_e_app_check_order(const char *file)
{
   Evas_List *l;

   for (l = _e_apps_all->subapps; l; l = l->next)
     {
	E_App *a;

	a = l->data;
	if (a->monitor)
	  {
	     /* This is a directory */
	     if (_e_app_order_contains(a, file))
	       _e_app_subdir_rescan(a);
	  }
     }
}

static int
_e_app_order_contains(E_App *a, const char *file)
{
   char buf[4096];
   int ret = 0;
   FILE *f;

   snprintf(buf, sizeof(buf), "%s/.order", a->path);
   if (!ecore_file_exists(buf)) return 0;
   f = fopen(buf, "rb");
   if (!f) return 0;

   while (fgets(buf, sizeof(buf), f))
     {
	int len;

	len = strlen(buf);
	if (len > 0)
	  {
	     if (buf[len - 1] == '\n')
	       {
		  buf[len - 1] = 0;
		  len--;
	       }
	     if (!strcmp(buf, file))
	       {
		  ret = 1;
		  break;
	       }
	  }
     }
   fclose(f);
   return ret;
}


static void
_e_app_resolve_file_name(char *buf, size_t size, const char *path, const char *file)
{
   size_t length;

   length = strlen(path);
   if (file[0] == '/')
      snprintf(buf, size, "%s", file);
   else if ((length > 0) && (path[length - 1] == '/'))
      snprintf(buf, size, "%s%s", path, file);
   else
      snprintf(buf, size, "%s/%s", path, file);
}
